有符号定点小数的31bit问题

文章讨论了定点小数在计算机中的表示,特别是有符号定点数(如S0.31)在处理时可能出现的溢出问题。一个算法故障由于在处理分数部分时未正确考虑符号位,导致除法运算中分母变为负数,影响结果。通过添加无符号整型转换修复了该问题,防止了符号溢出。加类型转换后的汇编代码显示了更复杂的溢出检查逻辑。
摘要由CSDN通过智能技术生成

背景

定点小数就是小数位恒定的小数,在信号处理等领域应用广泛,它的表示格式类似于S1.7(有符号,整数部分1bit,小数部分7bit)、U0.8(无符号,没有整数部分,小数部分8bit)。

假如小数是0.5,按U0.8来表示的话,在内存里的整数值就是128,因为U0.8把整数1细分成2^8份,0.5 = 1/2 * 1.0 = 1/2 * 256 = 128

同理,如果内存里的整数是128,按U0.8来解析,则就是128 / (2 ^ 8) = 1/2 = 0.5

故障

最近发现公司某个算法工作异常,算法同事修复后,我看了下代码改动,就是给表示分数部分的1 << N里的1前面加了个(unsinged int)的强制类型转换。看不太懂这个操作,1本来就是整型啊,为啥还要强转成整型?

float int2float(int vin, unsigned int frac_bits) {
    float vout = (float)vin / (float)(1 << frac_bits); // 1前面加(unsinged int)强转
    return vout;
}

分析

猜想

想起来故障是算法计算S0.31定点小数时出现的,S0.31有什么特别的地方吗?有,它是有符号数,即最高位是符号位,所以上述函数的frac_bits参数如果传个31,则1 << 31的结果就不是2147483648,而是-2147483648,后面就都错了。

验证猜想

阅读算法的代码,函数int2float的功能应该是将输入的整数vin(定点小数在内存里是按整数存储的),除以小数部分的最大值1<<frac_bits(因为在CPU里整数1被细分成2^frac_bits份),商就是CPU里的小数。

编写下列验证代码:

include<stdio.h>

#define FRAC_BIT (31)

float int2float(int vin, unsigned int frac_bits) {
    float vout = (float)vin / (float)(1 << frac_bits);
    return vout;
}

float int2float_ok(int vin, unsigned int frac_bits) {
    float vout = (float)vin / (float)((unsigned int)1 << frac_bits);
    return vout;
}

int main() {
    printf("a = %f\n", int2float(3351969, FRAC_BIT));
    printf("a = %f\n", int2float_ok(3351969, FRAC_BIT));
    return 0;
}

输出结果:

a = -0.001561
a = 0.001561

3351969按照S0.31来换算的话,应该是0.001561的,但因为它除以负的分数部,导致算出来的定点小数变成了负值!
如果将FRAC_BIT从31减小成30,则问题就不会出现:

a = 0.003122
a = 0.003122

可以看到,两个int2float函数的输出结果一致。
所以这是个仅在左移31bit才会触发的符号相关BUG

为什么加个类型强转就能解决?

我猜测是强制分数部变成无符号的,确保分母一定是正数,这样才能正确反映定点数在内存里的表示(可能是负整数),至于实际差异在哪,还是得看汇编代码(以x86-64汇编为例):
在这里插入图片描述
可以看出,加了个类型强转后,汇编代码增加了11条指令的额外处理,里面有循环右移和条件跳转,比原版复杂多了,以后有时间再分析。

总结

有符号数一定要注意溢出问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值