softfloat之f32_to_bf16

bfloat16_t f32_to_bf16( float32_t a )
{
    union ui32_f32 uA;
    uint_fast32_t uiA;
    bool sign;
    int_fast16_t exp;
    uint_fast32_t frac;
    struct commonNaN commonNaN;
    uint_fast16_t uiZ, frac16;
    union ui16_bf16 uZ;

    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    uA.f = a;
    uiA = uA.ui;
    sign = signF32UI( uiA );
    exp  = expF32UI( uiA );
    frac = fracF32UI( uiA );
    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    // infinity or NaN cases
    if ( exp == 0xFF ) {
        if ( frac ) {
            // NaN case
            softfloat_f32UIToCommonNaN( uiA, &commonNaN );
            uiZ = softfloat_commonNaNToBF16UI( &commonNaN );
        } else {
            // infinity case
            uiZ = packToBF16UI( sign, 0xFF, 0 );
        }
        goto uiZ;
    }
    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    // frac is a 24-bit mantissa, right shifted by 9
    // In the normal case, (24-9) = 15 are set 
    // 右移9位,还剩14位中间结果,并保留低9位是否为非零的进位
    // 个人理解,bit15预留,检测舍入时,会不会进位到2,需要指数+1的情况
    // bit14给到normal的隐含1,subnormal的隐含0,
    // bit0-bit13 移位剩下的14位尾数待处理
    frac16 = frac>>9 | ((frac & 0x1FF) != 0);
    // 0 case 
    if ( ! (exp | frac16) ) {
        uiZ = packToBF16UI( sign, 0, 0 );
        goto uiZ;
    }
    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    // softfloat_roundPackToBF16 exponent argument (2nd argument)
    // must correspond to the exponent of fracIn[13] bits
    // (fracIn is the 3rd and last argument) 
    // 如果是normal,则需要补齐隐含的1到bit14,subnormal补0
    uint_fast32_t mask = exp ? 0x4000 : 0x0; // implicit one mask added if input is a normal number
    // exponent for the lowest normal and largest subnormal should be equal
    // but is not in IEEE encoding so mantissa must be partially normalized
    // (by one bit) for subnormal numbers. Such that (exp - 1) corresponds
    // to the exponent of frac16[13]
    // 若f32为subnormal,需对齐bf16的normal格式(指数位-1,尾数左移1),统一给softfloat_roundPackToBF16处理
    frac16 = frac16 << (exp ? 0 : 1);
    return softfloat_roundPackToBF16( sign, exp - 1, frac16 | mask );
 uiZ:
    uZ.ui = uiZ;
    return uZ.f;

}

image
image

/** sig last significant bit is sig[7], the 7 LSBs will be used for rounding */
bfloat16_t
 softfloat_roundPackToBF16( bool sign, int_fast16_t exp, uint_fast16_t sig )
{
    uint_fast8_t roundingMode;
    bool roundNearEven;
    uint_fast8_t roundIncrement, roundBits;
    bool isTiny;
    uint_fast16_t uiZ;
    union ui16_bf16 uZ;

    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    // 如果是RNTE,因为当前还有14位数,bf16仅有7位尾数,需要舍弃低7位尾数
    // 7bit的中间值是0x40,RNTE规则:若被截断的位(roundBits)大于等于中间值则进位
    // 若为中间值(roundBits == 0x40),根据保留位是否为偶数调整,
    // 后面sig &= ~(uint_fast16_t)(!(roundBits ^ 0x40) & roundNearEven);为roundBits == 0x40时,导致的进位来回退最低位,强制结果为偶数
    roundingMode = softfloat_roundingMode;
    roundNearEven = (roundingMode == softfloat_round_near_even);
    roundIncrement = 0x40;
    if ( ! roundNearEven && (roundingMode != softfloat_round_near_maxMag) ) {
        roundIncrement =
            (roundingMode
                 == (sign ? softfloat_round_min : softfloat_round_max))
                ? 0x7F
                : 0;
    }
    roundBits = sig & 0x7F;
    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    if ( 0xFD <= (unsigned int) exp ) {
        if ( exp < 0 ) {
            /*----------------------------------------------------------------
            *----------------------------------------------------------------*/
            isTiny =
                (softfloat_detectTininess == softfloat_tininess_beforeRounding)
                    || (exp < -1) || (sig + roundIncrement < 0x8000);
            sig = softfloat_shiftRightJam32( sig, -exp );
            exp = 0;
            roundBits = sig & 0x7F;
            if ( isTiny && roundBits ) {
                softfloat_raiseFlags( softfloat_flag_underflow );
            }
        } else if ( (0xFD < exp) || (0x8000 <= sig + roundIncrement) ) {
            /*----------------------------------------------------------------
            *----------------------------------------------------------------*/
            softfloat_raiseFlags(
                softfloat_flag_overflow | softfloat_flag_inexact );
            uiZ = packToBF16UI( sign, 0xFF, 0 ) - ! roundIncrement;
            goto uiZ;
        }
    }
    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
    sig = (sig + roundIncrement)>>7;
    if ( roundBits ) {
        softfloat_exceptionFlags |= softfloat_flag_inexact;
#ifdef SOFTFLOAT_ROUND_ODD
        if ( roundingMode == softfloat_round_odd ) {
            sig |= 1;
            goto packReturn;
        }
#endif
    }
    sig &= ~(uint_fast16_t) (! (roundBits ^ 0x40) & roundNearEven);
    if ( ! sig ) exp = 0;
    /*------------------------------------------------------------------------
    *------------------------------------------------------------------------*/
 packReturn:
    uiZ = packToBF16UI( sign, exp, sig );
 uiZ:
    uZ.ui = uiZ;
    return uZ.f;

}

在SoftFloat库中,softfloat_roundPackToBF16函数处理多种舍入模式,每种模式对应不同的舍入增量(roundIncrement)和后续操作。以下是主要舍入规则的分析:


1. 舍入到最近偶数(Round to Nearest, Ties to Even)

  • 模式标识softfloat_round_near_even
  • roundIncrement0x40
  • 处理逻辑
    • 添加中间值(0x40),若被截断的位(roundBits)大于等于中间值则进位。
    • 若为中间值(roundBits == 0x40),根据保留位是否为偶数调整:
      sig &= ~(uint_fast16_t)(!(roundBits ^ 0x40) & roundNearEven);
      

2. 向零舍入(Round toward Zero)

  • 模式标识softfloat_round_minMag(或其他等效模式)
  • roundIncrement0
  • 处理逻辑
    • 直接截断多余位,不进行任何进位。
    • 示例:若roundBits = 0x7F(最大值),仍直接丢弃。

3. 向正无穷舍入(Round toward Positive Infinity)

  • 模式标识softfloat_round_max
  • roundIncrement符号相关
    • 若数为正(sign == false):roundIncrement = 0x7F(强制进位)。
    • 若数为负(sign == true):roundIncrement = 0(直接截断)。
  • 处理逻辑
    • 正数时,任何非零舍入位均导致进位(+0x7F确保进位)。
    • 负数时,直接截断(向下舍入)。

4. 向负无穷舍入(Round toward Negative Infinity)

  • 模式标识softfloat_round_min
  • roundIncrement符号相关
    • 若数为负(sign == true):roundIncrement = 0x7F(强制进位以向下舍入)。
    • 若数为正(sign == false):roundIncrement = 0(直接截断)。
  • 处理逻辑
    • 负数时,任何非零舍入位均导致进位(使数值更小)。
    • 正数时,直接截断(向下舍入)。

5. 就近舍入,中间值远离零(Round to Nearest, Ties Away from Zero)

  • 模式标识softfloat_round_near_maxMag
  • roundIncrement0x40(与RNTE相同,但处理不同)
  • 处理逻辑
    • 不进入符号相关分支,直接使用roundIncrement = 0x40
    • 中间值(roundBits == 0x40)时,直接进位(不调整奇偶性)。
    • 示例:0x0140sig + 0x40 = 0x0180 → 右移7位得0x03,保留该结果。

6. 奇进舍入(Round Odd)

  • 模式标识softfloat_round_odd(需启用宏SOFTFLOAT_ROUND_ODD
  • 处理逻辑
    • 强制将结果的最低位设为1,使其为奇数:
      if (roundingMode == softfloat_round_odd) {
          sig |= 1;
          goto packReturn;
      }
      
    • 适用于需要避免舍入误差累积的场景。

关键代码逻辑总结

// 根据舍入模式设置增量
roundIncrement = 0x40; // 默认:RNTE或RNTZ
if (!roundNearEven && (roundingMode != softfloat_round_near_maxMag)) {
    // 判断是否为RP或RM模式
    bool isDirectedMode = (roundingMode == (sign ? softfloat_round_min : softfloat_round_max));
    roundIncrement = isDirectedMode ? 0x7F : 0;
}

// 添加增量并右移
sig = (sig + roundIncrement) >> 7;

// 处理中间值(仅RNTE)
if (roundBits && roundNearEven) {
    sig &= ~(uint_fast16_t)(!(roundBits ^ 0x40));
}

示例对比

场景sig = 0x0140(二进制 0000_0001_0100_0000),roundBits = 0x40

  • RNTE
    • sig + 0x40 = 0x01800x03,检查中间值后可能调整为0x02(偶数)。
  • RNTZ
    • sig + 0x40 = 0x01800x03(直接保留,远离零)。
  • RP(正数)
    • roundIncrement = 0x7Fsig + 0x7F = 0x01BF0x03(进位)。
  • RM(负数)
    • roundIncrement = 0x7F → 同上,使结果更小。
  • RZ
    • roundIncrement = 00x0140 >> 7 = 0x02(直接截断)。

总结

  • roundIncrement的作用:通过添加不同增量,控制是否进位。
  • 符号敏感模式(RP/RM):根据符号动态选择0x7F0,实现方向性舍入。
  • 特殊处理(奇进):直接修改尾数位,确保结果为奇数。
posted @ 2025-04-09 21:39  King--jin  阅读(120)  评论(0)    收藏  举报