2017-10-05 117 views
0

我有需要我通过2用C来划分一个32位单精度浮点整数使用逐位运算(if语句和for循环也可以使用)的家庭作业。浮点的位表示为无符号整数,因此我们可以使用按位运算符来修改它们。我的问题是,我很难理解分割过程中的位发生了什么。我最初的计划是简单地将指数位向右移动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 
} 

此功能可以正确处理某些输入而不是全部,如上面给出的输入。请记住,我更多地询问在划分期间浮点数会发生什么变化,而不是简单地要求代码使其工作。

回答

4

你有一个很好的洞察力,通过二的幂缩放归一化,基数二,浮点数只影响指数(假设你既不溢或下溢),但正在执行的错误操作。将指数右移1将相当于将指数指数除以2。结果与原始数字的平方根大小相同。这是不是在所有你以后,除非原来的号码是4左右。

它可以帮助你写出来的二进制科学记数法的例子,因为这紧密对应机器表示。那么,假设您的原始号码N是1.01010x2 。

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

所以,是的,尾数和符号不改,但对指数的影响仅仅是由1


,以减少它对于你原来的程序,请注意,事实上,它并没有正确实施你描述的方法。它向右移动23指数位带来的最显著为单位的地方,然后通过一个更正确的执行你的操作,但随后转移回来位离开了。它应该只向左移动23,逆转原来的右移,使结果位回到正确的位置。

实际执行操作的作用是清除至少-显著指数位,这恰好是相当于减去1时偏置指数为奇数。这就是为什么它在一半时间内产生正确的答案。

+0

可能值得指出的是,通过指数的简单递减,IEEE-754“binary32”值的除以2仅适用于规范化的浮点数。额外的缩放对于正确处理非正常(子正常)是必要的。 – njuffa

+0

采取点,@ njuffa。我已经对我的评论进行了限定,以澄清它们适用于规范化投入。 –

0

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

这是除以2的最小正常float值,并在下面#3中详述。


代码有很多问题。

  1. 对于大多数有限数,递减而不是移位指数是正确的,因为所指出@John Bollinger好的答案当指数> 1

  2. exponent == 0,数量为sub-normal(或反规范),并且需要将其mantissa字段右移(/2)。指数保持为0.如果移出的位是1,那么除以2就不确切。根据四舍五入多,那么,mantissa调整 - 可能通过增加1

  3. exponent == 1,其结果将是分普通和正常的数字隐含位需要在mantissa场创建和向右移位(/2)。如上所述,这种转变可能会导致四舍五入。指数变为0.请注意,“舍入”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; 
    

unsigned可以是16,32,64,位等最好使用最小或精确宽度的类型。

#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跟在后面的情况下重写。它是经过测试的代码 - 通过许多组合和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

+0

舍入意味着什么在这种情况下意味着什么?即,如果round_bit为真,TBD_CODE_Handle_Rounding()会做什么? – jburn7

+0

@ jburn7它的代码留给你写。正如这里所回答的,“可能通过给mant添加1”。您的文章没有指定应该如何处理舍入。为了处理所有舍入模式(至少有4个)(https://www.gnu.org/software/libc/manual/html_node/Rounding.html))是相当多的代码。如果帖子明确规定了舍入目标,那更好。 – chux

+0

哦,我现在看到。我们没有被告知如何轮换,除此之外我们需要执行0.5 * f的按位等效,其中f是32位单精度浮点数。作为参考,我有另一个例子,当指数和符号位都是0,尾数== 0x3时,预期的结果是0x2,但是我的函数返回0x1。但是,我仍然不完全确定尾数比特在十进制数字方面的含义,所以我不确定这个例子是四舍五入的。 – jburn7