float - Punto flotante de media precisión en Java
bigdecimal java (5)
¿Hay una biblioteca de Java en algún lugar que pueda realizar cálculos en los números de media precisión IEEE 754 o convertirlos en una precisión doble?
Cualquiera de estos enfoques sería adecuado:
- Mantenga los números en formato de media precisión y calcule utilizando aritmética de números enteros y MicroFloat bits (como hace MicroFloat para precisión simple y doble)
- Realice todos los cálculos con precisión simple o doble, convirtiendo a / desde media precisión para la transmisión (en cuyo caso lo que necesito son funciones de conversión bien probadas).
Edición : la conversión debe ser 100% precisa: hay muchos NaN, infinitos y subnormales en los archivos de entrada.
Pregunta relacionada, pero para JavaScript: descomprimir flotadores de media precisión en Javascript
Antes de ver la solución publicada aquí, había preparado algo simple:
public static float toFloat(int nHalf)
{
int S = (nHalf >>> 15) & 0x1;
int E = (nHalf >>> 10) & 0x1F;
int T = (nHalf ) & 0x3FF;
E = E == 0x1F
? 0xFF // it''s 2^w-1; it''s all 1''s, so keep it all 1''s for the 32-bit float
: E - 15 + 127; // adjust the exponent from the 16-bit bias to the 32-bit bias
// sign S is now bit 31
// exp E is from bit 30 to bit 23
// scale T by 13 binary digits (it grew from 10 to 23 bits)
return Float.intBitsToFloat(S << 31 | E << 23 | T << 13);
}
Me gusta el enfoque en la otra solución publicada, sin embargo. Para referencia:
// notes from the IEEE-754 specification:
// left to right bits of a binary floating point number:
// size bit ids name description
// ---------- ------------ ---- ---------------------------
// 1 bit S sign
// w bits E[0]..E[w-1] E biased exponent
// t=p-1 bits d[1]..d[p-1] T trailing significant field
// The range of the encoding’s biased exponent E shall include:
// ― every integer between 1 and 2^w − 2, inclusive, to encode normal numbers
// ― the reserved value 0 to encode ±0 and subnormal numbers
// ― the reserved value 2w − 1 to encode +/-infinity and NaN
// The representation r of the floating-point datum, and value v of the floating-point datum
// represented, are inferred from the constituent fields as follows:
// a) If E == 2^w−1 and T != 0, then r is qNaN or sNaN and v is NaN regardless of S
// b) If E == 2^w−1 and T == 0, then r=v=(−1)^S * (+infinity)
// c) If 1 <= E <= 2^w−2, then r is (S, (E−bias), (1 + 2^(1−p) * T))
// the value of the corresponding floating-point number is
// v = (−1)^S * 2^(E−bias) * (1 + 2^(1−p) * T)
// thus normal numbers have an implicit leading significand bit of 1
// d) If E == 0 and T != 0, then r is (S, emin, (0 + 2^(1−p) * T))
// the value of the corresponding floating-point number is
// v = (−1)^S * 2^emin * (0 + 2^(1−p) * T)
// thus subnormal numbers have an implicit leading significand bit of 0
// e) If E == 0 and T ==0, then r is (S, emin, 0) and v = (−1)^S * (+0)
// parameter bin16 bin32
// -------------------------------------------- ----- -----
// k, storage width in bits 16 32
// p, precision in bits 11 24
// emax, maxiumum exponent e 15 127
// bias, E-e 15 127
// sign bit 1 1
// w, exponent field width in bits 5 8
// t, trailing significant field width in bits 10 23
El código de x4u codifica el valor 1 correctamente como 0x3c00 (ref: en.wikipedia.org/wiki/Half-precision_floating-point_format ). Pero el decodificador con mejoras de suavidad decodifica eso en 1,000122. La entrada de wikipedia dice que los valores enteros 0..2048 se pueden representar exactamente. No está bien...
Eliminar el "| 0x3ff"
del código toFloat garantiza que toFloat(fromFloat(k)) == k
para el entero k en el rango -2048..2048, probablemente a costa de un poco menos de suavidad.
Me interesaban los números de punto flotante positivos pequeños, así que construí esta variante con una mantisa de 12 bits, sin bit de signo y exponente de 4 bits, con sesgo 15 , de manera que puede representar números entre 0 y 1.00 (exclusivo) bastante bien. Tiene 2 bits de resolución en la mantisa extra, pero el mismo exponente bajo.
public static float toFloat(int hbits) {
int mant = hbits & 0x0fff; // 12 bits mantissa
int exp = (hbits & 0xf000) >>> 12; // 4 bits exponent
if (exp == 0xf) {
exp = 0xff;
} else {
if (exp != 0) { // normal value
exp += 127 - 15;
} else { // subnormal value
if (mant != 0) { // not zero
exp += 127 - 15;
// make it noral
exp++;
do {
mant <<= 1;
exp--;
} while ((mant & 0x1000) == 0);
mant &= 0x0fff;
}
}
}
return Float.intBitsToFloat(exp << 23 | mant << 11);
}
public static int fromFloat(float fval) {
int fbits = Float.floatToIntBits( fval );
int val = ( fbits & 0x7fffffff ) + 0x400; // rounded value
if( val < 0x32000000 ) // too small for subnormal or negative
return 0; // becomes 0
if( val >= 0x47800000 ) // might be or become NaN/Inf
{ // avoid Inf due to rounding
if( ( fbits & 0x7fffffff ) >= 0x47800000 )
{ // is or must become NaN/Inf
if( val < 0x7f800000 ) // was value but too large
return 0xf000; // make it +/-Inf
return 0xf000 | // remains +/-Inf or NaN
( fbits & 0x007fffff ) >>> 11; // keep NaN (and Inf) bits
}
return 0x7fff; // unrounded not quite Inf
}
if( val >= 0x38800000 ) // remains normalized value
return val - 0x38000000 >>> 11; // exp - 127 + 15
val = ( fbits & 0x7fffffff ) >>> 23; // tmp exp for subnormal calc
return ( ( fbits & 0x7f_ffff | 0x80_0000 ) // add subnormal bit
+ ( 0x800000 >>> val - 100 ) // round depending on cut off
>>> 124 - val ); // div by 2^(1-(exp-127+15)) and >> 11 | exp=0
}
Pruebas da:
Smallest subnormal float : 0.0000000149
Largest subnormal float : 0.0000610203
Smallest normal float : 0.0000610352
Smallest normal float + ups: 0.0000610501
E=1, M=fff (max) : 0.0001220554
Largest normal float : 0.0078115463
Normales:
0.9990000129 => 3f7fbe77 => eff8 => 0.9990234375 | error: 0.002%
0.8991000056 => 3f662b6b => ecc5 => 0.8990478516 | error: 0.006%
0.8091899753 => 3f4f2713 => e9e5 => 0.8092041016 | error: 0.002%
0.7282709479 => 3f3a6ff7 => e74e => 0.7282714844 | error: 0.000%
0.6554438472 => 3f27cb2b => e4f9 => 0.6553955078 | error: 0.007%
0.5898994207 => 3f1703a6 => e2e0 => 0.5898437500 | error: 0.009%
0.5309094787 => 3f07e9af => e0fd => 0.5308837891 | error: 0.005%
0.4778185189 => 3ef4a4a1 => de95 => 0.4778442383 | error: 0.005%
0.4300366640 => 3edc2dc4 => db86 => 0.4300537109 | error: 0.004%
0.3870329857 => 3ec62930 => d8c5 => 0.3870239258 | error: 0.002%
0.3483296633 => 3eb25844 => d64b => 0.3483276367 | error: 0.001%
0.3134966791 => 3ea082a3 => d410 => 0.3134765625 | error: 0.006%
0.2821469903 => 3e907592 => d20f => 0.2821655273 | error: 0.007%
0.2539322972 => 3e82036a => d040 => 0.2539062500 | error: 0.010%
0.2285390645 => 3e6a0625 => cd41 => 0.2285461426 | error: 0.003%
0.2056851536 => 3e529f21 => ca54 => 0.2056884766 | error: 0.002%
0.1851166338 => 3e3d8f37 => c7b2 => 0.1851196289 | error: 0.002%
0.1666049659 => 3e2a9a7e => c553 => 0.1665954590 | error: 0.006%
0.1499444693 => 3e198b0b => c331 => 0.1499328613 | error: 0.008%
0.1349500120 => 3e0a3056 => c146 => 0.1349487305 | error: 0.001%
0.1214550063 => 3df8bd67 => bf18 => 0.1214599609 | error: 0.004%
0.1093095019 => 3ddfdda9 => bbfc => 0.1093139648 | error: 0.004%
0.0983785465 => 3dc97ab1 => b92f => 0.0983734131 | error: 0.005%
0.0885406882 => 3db554d2 => b6ab => 0.0885467529 | error: 0.007%
0.0796866193 => 3da332bd => b466 => 0.0796813965 | error: 0.007%
0.0717179552 => 3d92e0dd => b25c => 0.0717163086 | error: 0.002%
0.0645461604 => 3d8430c7 => b086 => 0.0645446777 | error: 0.002%
0.0580915436 => 3d6df166 => adbe => 0.0580902100 | error: 0.002%
0.0522823893 => 3d56260f => aac5 => 0.0522842407 | error: 0.004%
0.0470541492 => 3d40bbda => a817 => 0.0470504761 | error: 0.008%
0.0423487313 => 3d2d75dd => a5af => 0.0423507690 | error: 0.005%
0.0381138586 => 3d1c1d47 => a384 => 0.0381164551 | error: 0.007%
0.0343024731 => 3d0c80c0 => a190 => 0.0343017578 | error: 0.002%
0.0308722258 => 3cfce7c0 => 9f9d => 0.0308723450 | error: 0.000%
0.0277850032 => 3ce39d60 => 9c74 => 0.0277862549 | error: 0.005%
0.0250065029 => 3cccda70 => 999b => 0.0250053406 | error: 0.005%
0.0225058515 => 3cb85e31 => 970c => 0.0225067139 | error: 0.004%
0.0202552658 => 3ca5ee5f => 94be => 0.0202560425 | error: 0.004%
0.0182297379 => 3c955688 => 92ab => 0.0182304382 | error: 0.004%
0.0164067633 => 3c86677a => 90cd => 0.0164070129 | error: 0.002%
0.0147660868 => 3c71ed75 => 8e3e => 0.0147666931 | error: 0.004%
0.0132894777 => 3c59bc1c => 8b38 => 0.0132904053 | error: 0.007%
0.0119605297 => 3c43f619 => 887f => 0.0119609833 | error: 0.004%
0.0107644768 => 3c305d7d => 860c => 0.0107650757 | error: 0.006%
0.0096880291 => 3c1eba8a => 83d7 => 0.0096874237 | error: 0.006%
0.0087192263 => 3c0edb16 => 81db => 0.0087184906 | error: 0.008%
0.0078473035 => 3c0091fa => 8012 => 0.0078468323 | error: 0.006%
0.0070625730 => 3be76d28 => 7cee => 0.0070629120 | error: 0.005%
0.0063563157 => 3bd048a4 => 7a09 => 0.0063562393 | error: 0.001%
0.0057206838 => 3bbb7493 => 776f => 0.0057210922 | error: 0.007%
0.0051486152 => 3ba8b5b7 => 7517 => 0.0051488876 | error: 0.005%
0.0046337536 => 3b97d6be => 72fb => 0.0046339035 | error: 0.003%
0.0041703782 => 3b88a7ab => 7115 => 0.0041704178 | error: 0.001%
0.0037533403 => 3b75fa9a => 6ebf => 0.0037531853 | error: 0.004%
0.0033780062 => 3b5d618a => 6bac => 0.0033779144 | error: 0.003%
0.0030402055 => 3b473e2f => 68e8 => 0.0030403137 | error: 0.004%
0.0027361847 => 3b335190 => 666a => 0.0027360916 | error: 0.003%
0.0024625661 => 3b216301 => 642c => 0.0024623871 | error: 0.007%
0.0022163095 => 3b113f81 => 6228 => 0.0022163391 | error: 0.001%
0.0019946785 => 3b02b927 => 6057 => 0.0019946098 | error: 0.003%
0.0017952106 => 3aeb4d46 => 5d6a => 0.0017952919 | error: 0.005%
0.0016156895 => 3ad3c58b => 5a79 => 0.0016157627 | error: 0.005%
0.0014541205 => 3abe9830 => 57d3 => 0.0014541149 | error: 0.000%
0.0013087085 => 3aab88f8 => 5571 => 0.0013086796 | error: 0.002%
0.0011778376 => 3a9a61ac => 534c => 0.0011777878 | error: 0.004%
0.0010600538 => 3a8af181 => 515e => 0.0010600090 | error: 0.004%
0.0009540484 => 3a7a191b => 4f43 => 0.0009540319 | error: 0.002%
0.0008586436 => 3a611698 => 4c23 => 0.0008586645 | error: 0.002%
0.0007727792 => 3a4a9455 => 4953 => 0.0007728338 | error: 0.007%
0.0006955012 => 3a36524c => 46ca => 0.0006954670 | error: 0.005%
0.0006259511 => 3a2416de => 4483 => 0.0006259680 | error: 0.003%
0.0005633560 => 3a13ae2e => 4276 => 0.0005633831 | error: 0.005%
0.0005070204 => 3a04e990 => 409d => 0.0005069971 | error: 0.005%
0.0004563183 => 39ef3e03 => 3de8 => 0.0004563332 | error: 0.003%
0.0004106865 => 39d75169 => 3aea => 0.0004106760 | error: 0.003%
0.0003696179 => 39c1c945 => 3839 => 0.0003696084 | error: 0.003%
0.0003326561 => 39ae6857 => 35cd => 0.0003326535 | error: 0.001%
0.0002993904 => 399cf781 => 339f => 0.0002993941 | error: 0.001%
0.0002694514 => 398d4527 => 31a9 => 0.0002694726 | error: 0.008%
0.0002425062 => 397e4946 => 2fc9 => 0.0002425015 | error: 0.002%
0.0002182556 => 3964db8b => 2c9b => 0.0002182424 | error: 0.006%
0.0001964300 => 394df8ca => 29bf => 0.0001964271 | error: 0.001%
0.0001767870 => 39395fe9 => 272c => 0.0001767874 | error: 0.000%
0.0001591083 => 3926d651 => 24db => 0.0001591146 | error: 0.004%
0.0001431975 => 39162749 => 22c5 => 0.0001432002 | error: 0.002%
0.0001288777 => 3907235b => 20e4 => 0.0001288652 | error: 0.010%
0.0001159900 => 38f33fa3 => 1e68 => 0.0001159906 | error: 0.001%
0.0001043910 => 38daec79 => 1b5e => 0.0001043975 | error: 0.006%
0.0000939519 => 38c50806 => 18a1 => 0.0000939518 | error: 0.000%
0.0000845567 => 38b15405 => 162b => 0.0000845641 | error: 0.009%
0.0000761010 => 389f986b => 13f3 => 0.0000761002 | error: 0.001%
0.0000684909 => 388fa2c6 => 11f4 => 0.0000684857 | error: 0.008%
0.0000616418 => 388145b2 => 1029 => 0.0000616461 | error: 0.007%
Y para las pruebas subnormales:
0.0000554776 => 3868b0a6 => 0e8b => 0.0000554770 | error: 0.001%
0.0000499299 => 38516bc8 => 0d17 => 0.0000499338 | error: 0.008%
0.0000449369 => 383c7a9a => 0bc8 => 0.0000449419 | error: 0.011%
0.0000404432 => 3829a18a => 0a9a => 0.0000404418 | error: 0.004%
0.0000363989 => 3818aafc => 098b => 0.0000364035 | error: 0.013%
0.0000327590 => 380966af => 0896 => 0.0000327528 | error: 0.019%
0.0000294831 => 37f7526e => 07bb => 0.0000294894 | error: 0.021%
0.0000265348 => 37de96fc => 06f5 => 0.0000265390 | error: 0.016%
0.0000238813 => 37c854af => 0643 => 0.0000238866 | error: 0.022%
0.0000214932 => 37b44c37 => 05a2 => 0.0000214875 | error: 0.026%
0.0000193438 => 37a24498 => 0512 => 0.0000193417 | error: 0.011%
0.0000174095 => 37920a89 => 0490 => 0.0000174046 | error: 0.028%
0.0000156685 => 37836fe1 => 041b => 0.0000156611 | error: 0.047%
0.0000141017 => 376c962e => 03b2 => 0.0000140965 | error: 0.037%
0.0000126915 => 3754ed8f => 0354 => 0.0000126958 | error: 0.034%
0.0000114223 => 373fa29a => 02ff => 0.0000114292 | error: 0.060%
0.0000102801 => 372c78be => 02b2 => 0.0000102818 | error: 0.016%
0.0000092521 => 371b3978 => 026d => 0.0000092536 | error: 0.016%
0.0000083269 => 370bb3b9 => 022f => 0.0000083297 | error: 0.034%
0.0000074942 => 36fb76b3 => 01f7 => 0.0000074953 | error: 0.014%
0.0000067448 => 36e2513a => 01c5 => 0.0000067502 | error: 0.081%
0.0000060703 => 36cbaf81 => 0197 => 0.0000060648 | error: 0.091%
0.0000054633 => 36b75127 => 016f => 0.0000054687 | error: 0.100%
0.0000049169 => 36a4fc3c => 014a => 0.0000049174 | error: 0.009%
0.0000044253 => 36947c9c => 0129 => 0.0000044256 | error: 0.009%
0.0000039827 => 3685a359 => 010b => 0.0000039786 | error: 0.103%
0.0000035845 => 36708c6d => 00f1 => 0.0000035912 | error: 0.188%
0.0000032260 => 36587e62 => 00d8 => 0.0000032187 | error: 0.228%
0.0000029034 => 3642d825 => 00c3 => 0.0000029057 | error: 0.080%
0.0000026131 => 362f5c21 => 00af => 0.0000026077 | error: 0.205%
0.0000023518 => 361dd2ea => 009e => 0.0000023544 | error: 0.112%
0.0000021166 => 360e0a9f => 008e => 0.0000021160 | error: 0.029%
0.0000019049 => 35ffacb7 => 0080 => 0.0000019073 | error: 0.127%
0.0000017144 => 35e61b71 => 0073 => 0.0000017136 | error: 0.047%
0.0000015430 => 35cf18b2 => 0068 => 0.0000015497 | error: 0.436%
0.0000013887 => 35ba6306 => 005d => 0.0000013858 | error: 0.208%
0.0000012498 => 35a7bf85 => 0054 => 0.0000012517 | error: 0.150%
0.0000011248 => 3596f92b => 004b => 0.0000011176 | error: 0.645%
0.0000010124 => 3587e040 => 0044 => 0.0000010133 | error: 0.091%
0.0000009111 => 357493a6 => 003d => 0.0000009090 | error: 0.236%
0.0000008200 => 355c1e7b => 0037 => 0.0000008196 | error: 0.054%
0.0000007380 => 35461b6e => 0032 => 0.0000007451 | error: 0.955%
0.0000006642 => 35324be3 => 002d => 0.0000006706 | error: 0.955%
0.0000005978 => 3520777f => 0028 => 0.0000005960 | error: 0.291%
0.0000005380 => 35106b8c => 0024 => 0.0000005364 | error: 0.291%
0.0000004842 => 3501fa64 => 0020 => 0.0000004768 | error: 1.522%
0.0000004358 => 34e9f5e7 => 001d => 0.0000004321 | error: 0.838%
0.0000003922 => 34d29083 => 001a => 0.0000003874 | error: 1.218%
0.0000003530 => 34bd820f => 0018 => 0.0000003576 | error: 1.315%
0.0000003177 => 34aa8ea7 => 0015 => 0.0000003129 | error: 1.499%
0.0000002859 => 34998063 => 0013 => 0.0000002831 | error: 0.978%
0.0000002573 => 348a26bf => 0011 => 0.0000002533 | error: 1.557%
0.0000002316 => 3478ac24 => 0010 => 0.0000002384 | error: 2.947%
0.0000002084 => 345fce20 => 000e => 0.0000002086 | error: 0.087%
0.0000001876 => 34496cb6 => 000d => 0.0000001937 | error: 3.264%
0.0000001688 => 3435483d => 000b => 0.0000001639 | error: 2.914%
0.0000001519 => 3423276a => 000a => 0.0000001490 | error: 1.933%
0.0000001368 => 3412d6ac => 0009 => 0.0000001341 | error: 1.933%
0.0000001231 => 3404279b => 0008 => 0.0000001192 | error: 3.144%
0.0000001108 => 33ede0e3 => 0007 => 0.0000001043 | error: 5.834%
0.0000000997 => 33d61732 => 0007 => 0.0000001043 | error: 4.629%
0.0000000897 => 33c0ae79 => 0006 => 0.0000000894 | error: 0.354%
0.0000000808 => 33ad69d3 => 0005 => 0.0000000745 | error: 7.735%
0.0000000727 => 339c1271 => 0005 => 0.0000000745 | error: 2.517%
0.0000000654 => 338c76ff => 0004 => 0.0000000596 | error: 8.874%
0.0000000589 => 337cd631 => 0004 => 0.0000000596 | error: 1.251%
0.0000000530 => 33638d92 => 0004 => 0.0000000596 | error: 12.501%
0.0000000477 => 334ccc36 => 0003 => 0.0000000447 | error: 6.249%
0.0000000429 => 33385163 => 0003 => 0.0000000447 | error: 4.168%
0.0000000386 => 3325e2d9 => 0003 => 0.0000000447 | error: 15.742%
0.0000000348 => 33154c29 => 0002 => 0.0000000298 | error: 14.265%
0.0000000313 => 33065e25 => 0002 => 0.0000000298 | error: 4.739%
0.0000000282 => 32f1dca9 => 0002 => 0.0000000298 | error: 5.846%
0.0000000253 => 32d9acfe => 0002 => 0.0000000298 | error: 17.606%
0.0000000228 => 32c3e87e => 0002 => 0.0000000298 | error: 30.673%
0.0000000205 => 32b0513e => 0001 => 0.0000000149 | error: 27.404%
0.0000000185 => 329eaf84 => 0001 => 0.0000000149 | error: 19.337%
0.0000000166 => 328ed12a => 0001 => 0.0000000149 | error: 10.375%
0.0000000150 => 3280890c => 0001 => 0.0000000149 | error: 0.416%
0.0000000135 => 32675d15 => 0001 => 0.0000000149 | error: 10.648%
0.0000000121 => 32503a2c => 0001 => 0.0000000149 | error: 22.943%
0.0000000109 => 323b678e => 0001 => 0.0000000149 | error: 36.603%
0.0000000098 => 3228aa00 => 0001 => 0.0000000149 | error: 51.781%
0.0000000088 => 3217cc33 => 0001 => 0.0000000149 | error: 68.646%
0.0000000080 => 32089e2e => 0001 => 0.0000000149 | error: 87.384%
0.0000000072 => 31f5e986 => 0000 => 0.0000000000 | error: 100.000%
Creé una clase java llamada HalfPrecisionFloat , que usa la solución de x4u. La clase tiene métodos de conveniencia y comprobación de errores. Va más allá y tiene métodos para devolver un Doble y Flotante desde el valor de media precisión de 2 bytes.
Espero que esto ayude a alguien.
==>
import java.nio.ByteBuffer;
/**
* Accepts various forms of a floating point half-precision (2 byte) number
* and contains methods to convert to a
* full-precision floating point number Float and Double instance.
* <p>
* This implemention was inspired by x4u who is a user contributing
* to .com.
* (https://.com/users/237321/x4u).
*
* @author dougestep
*/
public class HalfPrecisionFloat {
private short halfPrecision;
private Float fullPrecision;
/**
* Creates an instance of the class from the supplied the supplied
* byte array. The byte array must be exactly two bytes in length.
*
* @param bytes the two-byte byte array.
*/
public HalfPrecisionFloat(byte[] bytes) {
if (bytes.length != 2) {
throw new IllegalArgumentException("The supplied byte array " +
"must be exactly two bytes in length");
}
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
this.halfPrecision = buffer.getShort();
}
/**
* Creates an instance of this class from the supplied short number.
*
* @param number the number defined as a short.
*/
public HalfPrecisionFloat(final short number) {
this.halfPrecision = number;
this.fullPrecision = toFullPrecision();
}
/**
* Creates an instance of this class from the supplied
* full-precision floating point number.
*
* @param number the float number.
*/
public HalfPrecisionFloat(final float number) {
if (number > Short.MAX_VALUE) {
throw new IllegalArgumentException("The supplied float is too "
+ "large for a two byte representation");
}
if (number < Short.MIN_VALUE) {
throw new IllegalArgumentException("The supplied float is too "
+ "small for a two byte representation");
}
final int val = fromFullPrecision(number);
this.halfPrecision = (short) val;
this.fullPrecision = number;
}
/**
* Returns the half-precision float as a number defined as a short.
*
* @return the short.
*/
public short getHalfPrecisionAsShort() {
return halfPrecision;
}
/**
* Returns a full-precision floating pointing number from the
* half-precision value assigned on this instance.
*
* @return the full-precision floating pointing number.
*/
public float getFullFloat() {
if (fullPrecision == null) {
fullPrecision = toFullPrecision();
}
return fullPrecision;
}
/**
* Returns a full-precision double floating point number from the
* half-precision value assigned on this instance.
*
* @return the full-precision double floating pointing number.
*/
public double getFullDouble() {
return new Double(getFullFloat());
}
/**
* Returns the full-precision float number from the half-precision
* value assigned on this instance.
*
* @return the full-precision floating pointing number.
*/
private float toFullPrecision() {
int mantisa = halfPrecision & 0x03ff;
int exponent = halfPrecision & 0x7c00;
if (exponent == 0x7c00) {
exponent = 0x3fc00;
} else if (exponent != 0) {
exponent += 0x1c000;
if (mantisa == 0 && exponent > 0x1c400) {
return Float.intBitsToFloat(
(halfPrecision & 0x8000) << 16 | exponent << 13 | 0x3ff);
}
} else if (mantisa != 0) {
exponent = 0x1c400;
do {
mantisa <<= 1;
exponent -= 0x400;
} while ((mantisa & 0x400) == 0);
mantisa &= 0x3ff;
}
return Float.intBitsToFloat(
(halfPrecision & 0x8000) << 16 | (exponent | mantisa) << 13);
}
/**
* Returns the integer representation of the supplied
* full-precision floating pointing number.
*
* @param number the full-precision floating pointing number.
* @return the integer representation.
*/
private int fromFullPrecision(final float number) {
int fbits = Float.floatToIntBits(number);
int sign = fbits >>> 16 & 0x8000;
int val = (fbits & 0x7fffffff) + 0x1000;
if (val >= 0x47800000) {
if ((fbits & 0x7fffffff) >= 0x47800000) {
if (val < 0x7f800000) {
return sign | 0x7c00;
}
return sign | 0x7c00 | (fbits & 0x007fffff) >>> 13;
}
return sign | 0x7bff;
}
if (val >= 0x38800000) {
return sign | val - 0x38000000 >>> 13;
}
if (val < 0x33000000) {
return sign;
}
val = (fbits & 0x7fffffff) >>> 23;
return sign | ((fbits & 0x7fffff | 0x800000)
+ (0x800000 >>> val - 102) >>> 126 - val);
}
Y aquí están las pruebas unitarias.
import org.junit.Assert;
import org.junit.Test;
import java.nio.ByteBuffer;
public class TestHalfPrecision {
private byte[] simulateBytes(final float fullPrecision) {
HalfPrecisionFloat halfFloat = new HalfPrecisionFloat(fullPrecision);
short halfShort = halfFloat.getHalfPrecisionAsShort();
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.putShort(halfShort);
return buffer.array();
}
@Test
public void testHalfPrecisionToFloatApproach() {
final float startingValue = 1.2f;
final float closestValue = 1.2001953f;
final short shortRepresentation = (short) 15565;
byte[] bytes = simulateBytes(startingValue);
HalfPrecisionFloat halfFloat = new HalfPrecisionFloat(bytes);
final float retFloat = halfFloat.getFullFloat();
Assert.assertEquals(new Float(closestValue), new Float(retFloat));
HalfPrecisionFloat otherWay = new HalfPrecisionFloat(retFloat);
final short shrtValue = otherWay.getHalfPrecisionAsShort();
Assert.assertEquals(new Short(shortRepresentation), new Short(shrtValue));
HalfPrecisionFloat backAgain = new HalfPrecisionFloat(shrtValue);
final float backFlt = backAgain.getFullFloat();
Assert.assertEquals(new Float(closestValue), new Float(backFlt));
HalfPrecisionFloat dbl = new HalfPrecisionFloat(startingValue);
final double retDbl = dbl.getFullDouble();
Assert.assertEquals(new Double(startingValue), new Double(retDbl));
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidByteArray() {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putFloat(Float.MAX_VALUE);
byte[] bytes = buffer.array();
new HalfPrecisionFloat(bytes);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidMaxFloat() {
new HalfPrecisionFloat(Float.MAX_VALUE);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidMinFloat() {
new HalfPrecisionFloat(-35000);
}
@Test
public void testCreateWithShort() {
HalfPrecisionFloat sut = new HalfPrecisionFloat(Short.MAX_VALUE);
Assert.assertEquals(Short.MAX_VALUE, sut.getHalfPrecisionAsShort());
}
}
Puede usar Float.intBitsToFloat()
y Float.floatToIntBits()
para convertirlos desde y hacia valores flotantes primitivos. Si puede vivir con precisión truncada (en lugar de redondear), la conversión debería ser posible de implementar con solo unos pocos cambios de bit.
Ahora le he puesto un poco más de esfuerzo y resultó no tan simple como esperaba al principio. Esta versión ahora se ha probado y verificado en todos los aspectos que pueda imaginar y estoy muy seguro de que produce los resultados exactos para todos los valores de entrada posibles. Es compatible con el redondeo exacto y la conversión subnormal en cualquier dirección.
// ignores the higher 16 bits
public static float toFloat( int hbits )
{
int mant = hbits & 0x03ff; // 10 bits mantissa
int exp = hbits & 0x7c00; // 5 bits exponent
if( exp == 0x7c00 ) // NaN/Inf
exp = 0x3fc00; // -> NaN/Inf
else if( exp != 0 ) // normalized value
{
exp += 0x1c000; // exp - 15 + 127
if( mant == 0 && exp > 0x1c400 ) // smooth transition
return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16
| exp << 13 | 0x3ff );
}
else if( mant != 0 ) // && exp==0 -> subnormal
{
exp = 0x1c400; // make it normal
do {
mant <<= 1; // mantissa * 2
exp -= 0x400; // decrease exp by 1
} while( ( mant & 0x400 ) == 0 ); // while not normal
mant &= 0x3ff; // discard subnormal bit
} // else +/-0 -> +/-0
return Float.intBitsToFloat( // combine all parts
( hbits & 0x8000 ) << 16 // sign << ( 31 - 15 )
| ( exp | mant ) << 13 ); // value << ( 23 - 10 )
}
// returns all higher 16 bits as 0 for all results
public static int fromFloat( float fval )
{
int fbits = Float.floatToIntBits( fval );
int sign = fbits >>> 16 & 0x8000; // sign only
int val = ( fbits & 0x7fffffff ) + 0x1000; // rounded value
if( val >= 0x47800000 ) // might be or become NaN/Inf
{ // avoid Inf due to rounding
if( ( fbits & 0x7fffffff ) >= 0x47800000 )
{ // is or must become NaN/Inf
if( val < 0x7f800000 ) // was value but too large
return sign | 0x7c00; // make it +/-Inf
return sign | 0x7c00 | // remains +/-Inf or NaN
( fbits & 0x007fffff ) >>> 13; // keep NaN (and Inf) bits
}
return sign | 0x7bff; // unrounded not quite Inf
}
if( val >= 0x38800000 ) // remains normalized value
return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
if( val < 0x33000000 ) // too small for subnormal
return sign; // becomes +/-0
val = ( fbits & 0x7fffffff ) >>> 23; // tmp exp for subnormal calc
return sign | ( ( fbits & 0x7fffff | 0x800000 ) // add subnormal bit
+ ( 0x800000 >>> val - 102 ) // round depending on cut off
>>> 126 - val ); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
}
Implementé dos extensiones pequeñas en comparación con el libro porque la precisión general para flotadores de 16 bits es bastante baja, lo que podría hacer que las anomalías inherentes de los formatos de punto flotante sean perceptibles visualmente en comparación con los tipos de punto flotante más grandes, donde generalmente no se notan debido a la amplia precisión.
Las primeras son estas dos líneas en la función toFloat()
:
if( mant == 0 && exp > 0x1c400 ) // smooth transition
return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16 | exp << 13 | 0x3ff );
Los números de punto flotante en el rango normal del tamaño de tipo adoptan el exponente y, por tanto, la precisión de la magnitud del valor. Pero esto no es una adopción fluida, sucede en pasos: cambiar al siguiente exponente más alto da como resultado la mitad de la precisión. La precisión ahora permanece igual para todos los valores de la mantisa hasta el próximo salto al siguiente exponente más alto. El código de extensión anterior hace que estas transiciones sean más suaves al devolver un valor que se encuentra en el centro geográfico del rango de flotación de 32 bits cubierto para este valor de flotación de media particular. Cada valor medio flotante normal se asigna exactamente a 8192 valores flotantes de 32 bits. Se supone que el valor devuelto está exactamente en medio de estos valores. Pero en la transición del medio exponente de flotación, los valores inferiores de 4096 tienen el doble de precisión que los valores superiores de 4096 y, por lo tanto, cubren un espacio numérico que es solo la mitad de grande que en el otro lado. Todos estos 8192 valores flotantes de 32 bits se asignan al mismo valor medio flotante, por lo que la conversión de un medio flotante a 32 bits devuelve el mismo valor medio flotante independientemente de cuál de los 8192 valores intermedios de 32 bits se eligió. La extensión ahora se traduce en algo así como un medio paso más suave por un factor de sqrt (2) en la transición como se muestra en la imagen de la derecha a continuación, mientras que la imagen de la izquierda debe visualizar el paso agudo por un factor de dos sin anti-aliasing. Puede eliminar estas dos líneas del código de manera segura para obtener el comportamiento estándar.
covered number space on either side of the returned value:
6.0E-8 ####### ##########
4.5E-8 | #
3.0E-8 ######### ########
La segunda extensión está en la función fromFloat()
:
{ // avoid Inf due to rounding
if( ( fbits & 0x7fffffff ) >= 0x47800000 )
...
return sign | 0x7bff; // unrounded not quite Inf
}
Esta extensión amplía ligeramente el rango de números del formato de medio flotante al guardar unos valores de 32 bits que se promueven a Infinity. Los valores afectados son aquellos que habrían sido más pequeños que Infinity sin redondeo y se convertirían en Infinity solo debido al redondeo. Puede eliminar con seguridad las líneas que se muestran arriba si no desea esta extensión.
Intenté optimizar la ruta para los valores normales en la función fromFloat()
tanto como sea posible, lo que la hizo un poco menos legible debido al uso de constantes precomputadas y no desplazadas. No puse tanto esfuerzo en ''toFloat ()'' ya que de todos modos no superaría el rendimiento de una tabla de búsqueda. Entonces, si la velocidad realmente importa, podría usar la función toFloat()
solo para llenar una tabla de búsqueda estática con elementos 0x10000 y luego usar esta tabla para la conversión real. Esto es aproximadamente 3 veces más rápido con una máquina virtual del servidor x64 actual y aproximadamente 5 veces más rápido con la máquina virtual del cliente x86.
Pongo el código aquí en dominio público.