Java中的半精度浮点

fin*_*nnw 30 java floating-point precision ieee-754

在任何地方都可以使用Java库来执行IEEE 754半精度数字的计算或将它们转换为双精度数据吗?

这些方法中的任何一种都是合适的:

  • 将数字保持为半精度格式,并使用整数运算和比特计算进行计算(如MicroFloat用于单精度和双精度)
  • 以单精度或双精度执行所有计算,转换为半精度或从半精度进行传输(在这种情况下,我需要的是经过良好测试的转换函数.)

编辑:转换需要100%准确 - 输入文件中很多NaN,无穷大和次正规.


相关问题,但对于JavaScript:在Javascript中解压缩半精度浮点数

x4u*_*x4u 53

您可以使用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 )
}
Run Code Online (Sandbox Code Playgroud)
// 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
}
Run Code Online (Sandbox Code Playgroud)

本书相比,我实现了两个小扩展,因为16位浮点数的一般精度相当低,这可能使浮点格式的固有异常在视觉上可感知,而较大的浮点类型由于其足够的精度而通常不会被注意到.

第一个是toFloat()函数中的这两行:

if( mant == 0 && exp > 0x1c400 )  // smooth transition
    return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16 | exp << 13 | 0x3ff );
Run Code Online (Sandbox Code Playgroud)

类型大小的正常范围内的浮点数采用指数,因此精度为值的大小.但这并非顺利采用,而是按步骤进行:切换到下一个更高的指数会导致精度降低一半.对于尾数的所有值,精度现在保持不变,直到下一个跳到下一个更高的指数.上面的扩展代码通过返回该特定半浮点值的覆盖32位浮点范围的地理中心中的值,使这些转换更平滑.每个正常的半浮点值都精确映射到8192个32位浮点值.返回的值应该恰好位于这些值的中间.但是在半浮点指数的转换处,较低的4096值具有两倍于上4096值的精度,因此覆盖的数字空间仅为另一侧的一半.所有这些8192 32位浮点值映射到相同的半浮点值,因此将半浮点数转换为32位并返回导致相同的半浮点值,无论选择了哪个8192 中间 32位值.现在延伸通过SQRT的因子导致类似平滑的半工序(2)在如在右侧所示的过渡图象下面而左画面应该以可视化的尖锐步骤由两个因素不用抗混叠.您可以安全地从代码中删除这两行以获得标准行为.

covered number space on either side of the returned value:
       6.0E-8             #######                  ##########
       4.5E-8             |                       #
       3.0E-8     #########               ########
Run Code Online (Sandbox Code Playgroud)

第二个扩展是在fromFloat()功能中:

    {                                     // avoid Inf due to rounding
        if( ( fbits & 0x7fffffff ) >= 0x47800000 )
...
        return sign | 0x7bff;             // unrounded not quite Inf
    }
Run Code Online (Sandbox Code Playgroud)

此扩展稍微扩展了半浮点数格式的数字范围,方法是保存一些32位值,从而将其提升为Infinity.受影响的值是那些在没有舍入的情况下小于无穷大的值,并且由于舍入而仅变为无穷大.如果您不想要此扩展名,可以安全地删除上面显示的行.

我试图尽可能地优化fromFloat()函数中正常值的路径,这使得它由于使用预先计算和未移位的常量而变得不那么可读.我没有在'toFloat()'中投入太多精力,因为它无论如何都不会超过查找表的性能.因此,如果速度真的很重要,可以使用该toFloat()函数仅填充具有0x10000元素的静态查找表,而不是使用此表进行实际转换.使用当前的x64服务器虚拟机大约快3倍,使用x86客户端虚拟机大约快5倍.

我把代码放在公共领域.

  • 我正在复制误解为答案的buttonius注释:“ x4u的代码将值1正确编码为0x3c00(参考:https://en.wikipedia.org/wiki/Half-precision_floating-point_format)。但是解码器具有平滑性改进将其解码为1.000122。维基百科条目说可以精确表示整数值0..2048。“ toFloat代码中的0x3ff”确保,对于-2048..2048范围内的整数k,toFloat(fromFloat(k))== k,可能以降低平滑度为代价。” Benoit在一个现已删除的答案中也做了同样的观察。 (2认同)
  • 为什么不接受并返回`short`?我意识到 `short` 有点像二等公民,但 `short[]` 和 `ShortBuffer` 是半人半自然而快速的容器。 (2认同)