改变精度的C语言程序,C语言移位运算符精度问题

如果有写过单片机或者嵌入式系统的裸机驱动,很多编译器对于除(/)操作需要执行多个指令才能完成。于是为了节省指令,经常把除(/)的操作转换成向右移位来完成。但是,在运用移位运算符的过程中需要考虑到精度的问题,有可能在移位的过程中,损失掉了精度。

正是没注意到该问题的存在,导致了在一个项目中一个bug的产生。特此记录。

等价左右移位

大家都知道左移n位相当于乘以2的n次方,右移m位相当于除以2的m次方。

1、当左移n位之后再右移m位(n>m),我们可以等价为:直接左移(n-m)位

测试代码如下:

#include

void main(void)

{

short a = 11; // 0000 0000 0000 1011 -> 11

short b = 2; // 0000 0000 0000 0010 -> 2

short c = 0, d = 0, e = 0, f = 0;

// 先左移3位再右移1位

c = a<<3; // 0000 0000 0101 1000 -> 88

d = b<<3; // 0000 0000 0001 0000 -> 16

e = c>>1; // 0000 0000 0010 1100 -> 44

f = d>>1; // 0000 0000 0000 1000 -> 8

printf("shift left 3 then shift right 1\n");

printf("c = %d, d = %d\n", c, d);

printf("e = %d, f = %d\n", e, f);

printf("\n");

// 直接左移2位

e = a<<2; // 0000 0000 0010 1100 -> 44

f = b<<2; // 0000 0000 0000 1000 -> 8

printf("shift left 2\n");

printf("e = %d, f = %d\n", e, f);

printf("\n");

getchar();

}

运行如下:

0818b9ca8b590ca3270a3433284dd417.png

最终结果等价于a*4 = 44,b*4 = 8;

2、当左移n位之后再右移m位(n

#include

void main(void)

{

short a = 11; // 0000 0000 0000 1011 -> 11

short b = 2; // 0000 0000 0000 0010 -> 2

short c = 0, d = 0, e = 0, f = 0;

// 先左移1位再右移3位

c = a<<1; // 0000 0000 0001 0110 -> 22

d = b<<1; // 0000 0000 0000 0100 -> 4

e = c>>3; // 0000 0000 0000 0010 -> 2

f = d>>3; // 0000 0000 0000 0000 -> 0

printf("shift left 1 then shift right 3\n");

printf("c = %d, d = %d\n", c, d);

printf("e = %d, f = %d\n", e, f);

printf("\n");

// 直接右移2位

e = a>>2; // 0000 0000 0000 0010 -> 2

f = b>>2; // 0000 0000 0000 0000 -> 0

printf("shift right 2\n");

printf("e = %d, f = %d\n", e, f);

printf("\n");

getchar();

}

运行结果如图:

0818b9ca8b590ca3270a3433284dd417.png

运算结果等价于:a/4 = 2, b/4 = 0。

左右移位先后顺序

前面那些都是一些基础性的东西,大家都知道。只有这个最扯淡,稍微不注意有可能就产生了bug。

在我们的理解中,整数a左移n位再右移m位,在算式上是这么做的:a乘以2的n次方再除以2的m次方。数学老师教过我们,乘除同时存在是没有运算的先后顺序的。于是我们就想,上面的算式可以等价为a除以2的m次方再乘以2的n次方,也就是说等价于右移m位再左移n位。真的可以这样等价吗?我的回答是:

有时可以,有时不行。只有当整数a的右移m位没有损失掉1,那么就可以这么做。说得直白一点就是你如果想要右移m位,那么整数a在低位至少要有m个0

#include

void main(void)

{

short a = 11; // 0000 0000 0000 1011 -> 11

short b = 2; // 0000 0000 0000 0010 -> 2

short c = 0, d = 0, e = 0, f = 0;

// 先左移3位再右移1位

c = a<<3; // 0000 0000 0101 1000 -> 88

d = b<<3; // 0000 0000 0001 0000 -> 16

e = c>>1; // 0000 0000 0010 1100 -> 44

f = d>>1; // 0000 0000 0000 1000 -> 8

printf("shift left 3 then shift right 1\n");

printf("c = %d, d = %d\n", c, d);

printf("e = %d, f = %d\n", e, f);

printf("\n");

// 先右移1位再左移3位

c = a>>1; // 0000 0000 0000 0101 -> 5 // 注意,此处最低位被弄没了

d = b>>1; // 0000 0000 0000 0001 -> 1

e = c<<3; // 0000 0000 0010 1000 -> 40

f = d<<3; // 0000 0000 0000 1000 -> 8

printf("shift right 1 then shift left 3\n");

printf("c = %d, d = %d\n", c, d);

printf("e = %d, f = %d\n", e, f);

printf("\n");

getchar();

}

输出结果为:

0818b9ca8b590ca3270a3433284dd417.png

从上面的运算结果可以看出,11左移3位再右移1位的结果是44,11右移1位再左移3位的结果是40.两者不一致。因为11的最低位为1,右移1位之后1就没了,这就导致了精度的损失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值