分裂期间浮点数会发生什么变化?

时间:2017-10-05 03:18:32

标签: c floating-point bit-manipulation bitwise-operators bit

我有一个家庭作业,要求我使用按位运算将32位单精度浮点整数除以C(如果也可以使用语句和for循环)。 float的位表示为无符号整数,因此我们可以使用按位运算符对它们进行修改。我的问题是,我很难理解分裂期间的比特究竟发生了什么。我的初步计划只是将指数位右移1,同时保持符号和尾数位相同,但这没有奏效。例如,当我的函数给出由0x800000表示的位时,我的函数返回0x00000000,因为右移指数将导致所有位为0.但是,根据作业的测试驱动程序,在这种情况下的正确答案是0x00400000。这真让我困惑,因为我不确定指数位如何或为何会转换为尾数位。

unsigned divideFloatBy2(unsigned uf){
//copy the sign bit
unsigned signBit = (1 << 31) & uf;

//copy mantissa
unsigned mantissa = ~0;
mantissa >>= 9;
mantissa &= uf;

//copy exponent
unsigned mask = 0xFF;
mask <<= 23;
unsigned exponent = (uf & mask);
exponent >>= 23;

exponent >>= 1; //right shift to divide by 2;

exponent <<= 24;

//combine all again
unsigned ret = signBit | exponent | mantissa;
return ret; //will be interpreted as float later
}

此功能对某些输入正常工作,但不是全部,例如上面给出的输入。请记住,我更多地询问在分割期间浮点数会发生什么,而不是我只是要求代码来完成这项工作。

2 个答案:

答案 0 :(得分:4)

你有一个很好的见解,即按2的幂缩放归一化的,基数为2的浮点数只会影响指数(假设你既不溢出也不下溢),但是你正在执行错误的操作。将指数右移1相当于将它 - 指数 - 除以2。结果与原始数字的平方根具有相同的大小。除非原始数字大约为4,否则这根本不是你所追求的。

它可能会帮助您以二进制科学记数法写出示例,因为它与机器表示形式紧密对应。那么假设您的原始数字N为1.01010x2 110

N / 2 = N * 2-1
      = 1.01010x2110 * 2-1
      = 1.01010x2110-1
      = 1.01010x2101

所以是的,尾数和符号不会改变,但对指数的影响只是将它减少1。

关于原始程序,请注意事实上它并没有正确实现您描述的方法。它将指数位向右移动23以使单位位置具有最低有效位,然后再向右移动一位以实现操作,但随后向左移位 24 位。它应该向左移动仅23,反转原来的右移,使结果位回到正确的位置。

您实际执行的操作的效果是清除最不显着的指数位,这恰好相当于当偏置指数为奇数时减1。这就是为什么它会在一半时间内产生正确的答案。

答案 1 :(得分:0)

  

当......给定0x800000时,我的函数返回0 ....,正确的答案是0x00400000。

这是将最小正常 float值除以2,详见下面的#3。

代码存在许多问题。

  1. 对于大多数有限数,当指数为&gt;时,@John Bollinger良好答案指出,递减而不是移位指数是正确的。 1

  2. exponent == 0时,数字为sub-normal(或非常规)且需要将其mantissa字段右移(/2)。指数保持为0.如果移出的位为1,则除以2则不精确。根据更多的舍入,然后调整mantissa - 可能通过添加1.

  3. exponent == 1时,结果将是次正常的,并且需要在mantissa字段中创建正常数字的隐含位并向右移位(/2) 。如上所述,这种转变可能导致四舍五入。指数变为0.注意&#34;舍入&#34; mant可能超过mant 0x7FFFFF的最大值,然后需要调整字段。

  4. exponent == MAX (255)时,数字不是有限的(它是无穷大或非数字),应该单独留下。

  5. 1 << 31之类的代码更好地定义为:

    // unsigned signBit = (1 << 31) & uf;
    unsigned signBit = (1u << 31) & uf;   // Use an unsigned mask
    unsigned signBit = (1LU << 31) & uf;  // unsigned may be 16 bit.
    // or better yet
    unsigned signBit = uf & 0x80000000;
    
  6. mantissa推导的角点缺点在于它依赖于(绝大多数)2的补码。便携式替代方案:

    // unsigned mantissa = ~0;  Incorrect mask in `mantissa` when `int` is not 2's comp.
    // unsigned mantissa = -1;  correct all bits set.
    // mantissa >>= 9;
    // mantissa &= uf;
    // or simply use
    unsigned mantissa = 0x7FFFFF & uf;
    
  7. unsigned可能是16,32,64,bit等。最好使用最小或精确宽度类型。

    #define SIGN_MASK 0x80000000
    #define EXPO_MASK 0x7F800000
    #define MANT_MASK 0x007FFFFF
    
    #define EXPO_SHIFT 23
    #define EXPO_MAX         (EXPO_MASK >> EXPO_SHIFT)
    #define MANT_IMPLIED_BIT (MANT_MASK + 1u)
    
    uint32_t divideFloatBy2(uint32_t uf){
      unsigned sign = uf & SIGN_MASK;
      unsigned expo = uf & EXPO_MASK;
      unsigned mant = uf & MANT_MASK;
    
      expo >>= EXPO_SHIFT;
      // when the number is not an infinity nor NaN
      if (expo != EXPO_MAX) {
        if (expo > 1) {
          expo--;  // this is the usual case
        } else {
          if (expo == 1) {
            mant |= MANT_IMPLIED_BIT;
          }
          expo = 0;
          unsigned round_bit = mant & 1;
          mant /= 2;
    
          if (round_bit) {
            TBD_CODE_Handle_Rounding(round_mode, sign, &expo, &mant);
          }
        }
        expo <<= EXPO_SHIFT;
        uf = sign | expo | mant;
      }
      return uf;
    }
    

    OP稍后评论了exponent ,sign 0, mantissa == 0x3, expected result is 0x2, but my returning 1.,因此舍入模式可能是FE_TONEAREST或可能是FE_UPWARD

    expo <= 1之后重写此案例。它是经过测试的代码 - 通过许多2 32 组合并具有4种舍入模式。

    请注意,当some_float/2.0f计算时,它可能会影响浮点环境状态位。我最初做得很好但是从这篇帖子中删除了代码 - 如果感兴趣的话就联系。

        } else {
          if (expo == 1) {
            expo = 0;
            mant |= MANT_IMPLIED_BIT;
          }
          // Divided by 2 result inexact?
          if (mant % 2) {
            mant /= 2;
            // Determine how to round
            switch (fegetround()) {
              case FE_DOWNWARD:
                if (sign) mant++;
                break;
              case FE_TOWARDZERO:
                break;
              case FE_UPWARD:
                if (!sign) mant++;
                break;
              default: // When mode is not known, act like FE_TONEAREST
                // fall through
              case FE_TONEAREST:
                if (mant & 1) mant++;
                break;
            }
            if (mant >= MANT_IMPLIED_BIT) {
              mant = 0;
              expo++;
            }
          } else {
            mant /= 2;
          }
        }
    

    有关舍入模式的详细信息,请搜索FE_...宏或here