您可以使用Float.intBitsToFloat() 和Float.floatToIntBits() 将它们与原始浮点值相互转换。如果您可以接受截断精度(而不是舍入),则应该可以通过几个位移位来实现转换。
我现在在这方面付出了更多努力,但结果并不像我一开始所期望的那么简单。这个版本现在在我能想象的每一个方面都经过了测试和验证,我非常有信心它会为所有可能的输入值产生准确的结果。它支持任意方向的精确舍入和次正规转换。
// 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
}
与 book 相比,我实现了两个小的扩展,因为 16 位浮点数的一般精度相当低,与较大的浮点类型相比,浮点格式的固有异常可以在视觉上感知由于足够的精度,通常不会被注意到。
第一行是toFloat()函数中的这两行:
if( mant == 0 && exp > 0x1c400 ) // smooth transition
return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16 | exp << 13 | 0x3ff );
类型大小的正常范围内的浮点数采用指数,因此精度为值的大小。但这并不是一个顺利的采用,它是分步进行的:切换到下一个更高的指数会导致一半的精度。现在,尾数的所有值的精度保持不变,直到下一次跳转到下一个更高的指数。上面的扩展代码通过为这个特定的半浮点值返回一个位于覆盖的 32 位浮点范围的地理中心的值,使这些转换更加平滑。每个正常的半浮点值都映射到 8192 个 32 位浮点值。返回的值应该正好在这些值的中间。但是在半浮点指数的过渡处,较低的 4096 值的精度是较高的 4096 值的两倍,因此覆盖的数字空间仅为另一侧的一半。所有这些 8192 个 32 位浮点值都映射到相同的半浮点值,因此将半浮点转换为 32 位并返回会导致相同的半浮点值,无论 8192 中间 32 位值中的哪一个是选择。扩展现在会在过渡时产生类似平滑的半步长 sqrt(2) 的结果,如下面右侧 picture 所示,而左侧 picture 应该是在没有抗锯齿的情况下将锐步可视化为两倍。您可以安全地从代码中删除这两行以获得标准行为。
covered number space on either side of the returned value:
6.0E-8 ####### ##########
4.5E-8 | #
3.0E-8 ######### ########
第二个扩展在fromFloat()函数中:
{ // avoid Inf due to rounding
if( ( fbits & 0x7fffffff ) >= 0x47800000 )
...
return sign | 0x7bff; // unrounded not quite Inf
}
此扩展通过保存一些 32 位值从提升到 Infinity 略微扩展了半浮点格式的数字范围。受影响的值是那些在没有四舍五入的情况下会小于 Infinity 并且仅由于四舍五入而变为 Infinity 的值。如果你不想要这个扩展,你可以安全地删除上面显示的行。
我尝试尽可能优化fromFloat() 函数中正常值的路径,由于使用了预计算和未移位的常量,这使得它的可读性降低了一些。我没有在 'toFloat()' 上投入太多精力,因为它无论如何都不会超过查找表的性能。因此,如果速度真的很重要,可以使用toFloat() 函数仅使用 0x10000 元素填充静态查找表,而不是使用该表进行实际转换。这比当前的 x64 服务器虚拟机快 3 倍左右,使用 x86 客户端虚拟机快 5 倍左右。
我特此将代码放到公共领域。