我有一个家庭作业,要求我使用按位运算将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
}
此功能对某些输入正常工作,但不是全部,例如上面给出的输入。请记住,我更多地询问在分割期间浮点数会发生什么,而不是我只是要求代码来完成这项工作。
答案 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。
代码存在许多问题。
对于大多数有限数,当指数为&gt;时,@John Bollinger良好答案指出,递减而不是移位指数是正确的。 1
当exponent == 0
时,数字为sub-normal(或非常规)且需要将其mantissa
字段右移(/2
)。指数保持为0.如果移出的位为1,则除以2则不精确。根据更多的舍入,然后调整mantissa
- 可能通过添加1.
当exponent == 1
时,结果将是次正常的,并且需要在mantissa
字段中创建正常数字的隐含位并向右移位(/2
) 。如上所述,这种转变可能导致四舍五入。指数变为0.注意&#34;舍入&#34; mant
可能超过mant
0x7FFFFF
的最大值,然后需要调整字段。
当exponent == MAX (255)
时,数字不是有限的(它是无穷大或非数字),应该单独留下。
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;
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,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。