不解释,直接上图,自己测试
网友评论:这不能算是一个 bug, 只是超出人们的预期而已。整数除法有着舍入方向的问题,向上舍入与向下舍入都不能说成是错误。
很多编译器,特别是没有高速硬件除法cpu上的编译器,会对 /2, /4, /8等等优化,用移位来代替真正 ...
highgear 发表于 2011-9-26 21:15
没开优化也用移位,实在是不应该。。。。
网友评论:楼上,C的标准并没有规定负整数除法的方向,实际上,早期的标准声明负整数除法的舍入方向取决与cpu 以及编译器。
因此,-5/4的结果为-1还是-2都不能算是一个错误,都是正确的,只要遵循了同一个方向的舍入。很多的c/c++书中特地对负整数除法的舍入方向提出了告诫。
网友评论:highgear老师解释的透彻,学习了
网友评论:
keil 这确实不能算是一个 bug, 只是超出人们的预期而已。
整数除法有着舍入方向的问题,向上舍入与向下舍入都不能说成是错误。
网友评论:楼上,C的标准并没有规定负整数除法的方向,实际上,早期的标准声明负整数除法的舍入方向取决与cpu 以及编译器。
因此,-5/4的结果为-1还是-2都不能算是一个错误,都是正确的,只要遵循了同一个方向的舍入。很多的c/ ...
highgear 发表于 2011-9-27 02:10
还不能算BUG算什么?我看你都没仔细看大家的讨论!是不是BUG关键在于:
| -5/4 | > | -5/3
网友评论:|上面大家有很多人认为这不是个BUG,也许有些道理
但请大家再去测试以下的代码,
signed char x,y,z,a;
z = -5;
a = 4;
x = z/a;
y = z%a;
如果按以上有些理论,至少KEIL应该结果一样
但真正的结果呢
网友评论:还有
试试
signed char x;
x = -5/4
网友评论:上面大家有很多人认为这不是个BUG,也许有些道理
但请大家再去测试以下的代码,
signed char x,y,z,a;
z = -5;
a = 4;
x = z/a;
y = z%a;
如果按以上有些理论,至少KEIL应该结果一样
但真正的结果呢 ...
ayb_ice 发表于 2011-9-27 08:14
只因为 z/a用的是库函数,而不是移位运算!
如果库函数也遵循同样的舍入方向也没问题,看到这个问题我的第一想法就是:是不是舍入方向问题?
所以才做了这些测试,关键就是 z(-5)/4 != z(-5)/3!
网友评论:楼上,C的标准并没有规定负整数除法的方向,实际上,早期的标准声明负整数除法的舍入方向取决与cpu 以及编译器。
因此,-5/4的结果为-1还是-2都不能算是一个错误,都是正确的,只要遵循了同一个方向的舍入。很多的c/ ...
highgear 发表于 2011-9-27 02:10
我知道的是,C和C++向0方向舍入。
没啥好争的了,大家都清楚,是优化导致C51编译器采用了移位方法进行运算,导致了“非预期的结果”。这本身就是不应该的事情。而同样的ARM编译器却没有类似情况。大家使用int型去试试就知道了。
同时这也告戒我们,编写程序要严谨,为避免出现可能的“非预期结果”,可以参照13楼的方法,杜绝摸棱两可的不同结果,这才是编程的思想。
网友评论:还有
试试
signed char x;
x = -5/4
ayb_ice 发表于 2011-9-27 08:21
x = -5/4就不关keil事了,这个时候(-5/4)是PC机
计算的。
网友评论:也许真的不是BUG,只是超出了人们的预期而已,设计的人没想到会有人直接把立即数写到程序里;
就好像设计火车的人们根本没预期天上会下雨打雷;是在是属于使用错误;
网友评论:也许真的不是BUG,只是超出了人们的预期而已,设计的人没想到会有人直接把立即数写到程序里;
就好像设计火车的人们根本没预期天上会下雨打雷;是在是属于使用错误; ...
mcu5i51 发表于 2011-9-27 08:30
这个说法太牵强了,每个BUG都是超出设计者的预期的!
而且错不在移位运算,假如库函数用的也是同一个舍入方向那就不是BUG了!而偏偏它们用的是不同的舍入方向。
网友评论:BUG就是BUG
至少你KEIL C51应该保持一致
这个不是BUG,那另外的不一致的那个就是BUG
网友评论:也许真的不是BUG,只是超出了人们的预期而已,设计的人没想到会有人直接把立即数写到程序里;
就好像设计火车的人们根本没预期天上会下雨打雷;是在是属于使用错误; ...
mcu5i51 发表于 2011-9-27 08:30
开玩笑都是
下面的程序难道大家没有用过
x/10
x%10
x/16
x%16
网友评论:t.jm:
这是优化所产生的问题, y = x/4 与 y = x/a 的结果不同并不出乎意料。很多编译器对 /2, /4, /8 等常数分母都是直接使用移位而不是除法,这也不是超出预期,反而是在预期之内。
对于分子分母都是变量,keil 中 -5/4 = -5/3 的结果都是-1.
网友评论:t.jm:
这是优化所产生的问题, y = x/4 与 y = x/a 的结果不同并不出乎意料。很多编译器对 /2, /4, /8 等常数分母都是直接使用移位而不是除法,这也不是超出预期,反而是在预期之内。
对于分子分母都是变量,keil 中 ...
highgear 发表于 2011-9-27 09:00
你说的也很对,但其它编译器都是正确的,一致的,我至少试过4~5种不同的编译器
优化都不能保证结果正确,还谈什么优化,还是BUG
网友评论:呵呵,这么说吧,我在不同的编译器下做过大量的数**算,除了vc++, 嵌入式中基本上都是使用整数运算,但我从不使用 /2, /4, /8等等,都是直接使用 >>1, >>2, >>3等,了解编译器的行为是一个嵌入式程序员基本功课。
网友评论:t.jm:
这是优化所产生的问题, y = x/4 与 y = x/a 的结果不同并不出乎意料。很多编译器对 /2, /4, /8 等常数分母都是直接使用移位而不是除法,这也不是超出预期,反而是在预期之内。
对于分子分母都是变量,keil 中 ...
highgear 发表于 2011-9-27 09:00
晕!这根本不关优化的事,优化也是好的行为,肯定的!
只要它能保证:(-5/4) == (-5/3),它就不是BUG了,也就没有什么危害至多是计算精度问题,
然而:(-5/4) != (-5/3),而且|(-5/4)| > |(-5/3)|这才是致命的危害!
再讨论这个问题前,舍入问题我是有考虑的,你应该多多考虑:
(-5/4) != (-5/3),而且|(-5/4)| > |(-5/3)|的严重性!
网友评论:ayb_ice: 你不能说“优化都不能保证结果正确”, 只能说结果不是你所预期的。因为,-5/4为-1或是 -2 都是正确的。
网友评论:呵呵,这么说吧,我在不同的编译器下做过大量的数**算,除了vc++, 嵌入式中基本上都是使用整数运算,但我从不使用 /2, /4, /8等等,都是直接使用 >>1, >>2, >>3等,了解编译器的行为是一个嵌入式程序员基本功课。 ...
highgear 发表于 2011-9-27 09:12
恕我直言,你这点技巧什么都不算,甚至你都搞错方向了,
是除法(/)能代替移位(>>),而不是移位(>>)代替除法(/),我要/3怎么办?
网友评论:ayb_ice: 你不能说“优化都不能保证结果正确”, 只能说结果不是你所预期的。因为,-5/4为-1或是 -2 都是正确的。
highgear 发表于 2011-9-27 09:18
如果只考虑结果这个确实没有错,但起码必须保持一致吧
不要一会=-1,一会又等于-2吧
从数学上讲难道
x = -5/4;
和
y = -5;
z = 4
x = y/z;
是不一样的
网友评论:改成int型就对了。
网友评论:t.jm: 其实全整数运算需要很多技巧,而且必须对cpu以及编译器有足够的了解,否则就会出现什么 “诡异”,“bug"之类的困惑。
除数是变量时,我想不会有问题,因为编译器一般会使用除法器。对于除数是常数,那么"什么都不算的技巧"相当有用,坦白的说,除了vc++, 我几乎不用常数除法,而是用乘法代替,例如: x /3 可为 x * (65536/3) >> 16 (cpu 具有16x16的乘法器) 或是 x * (256/3) >> 8。
再解释一次:
楼主的情况仅仅是在除数是常数2,4, 8 等而且被除数为负值的情况下出现,是因为编译器优化使用了因为而不是使用除法器,这其实是有经验的程序员所预期的。同时,除非不关注效率,最好不要直接使用常数除法,可以使用 >> 以及乘法代替。
网友评论:是因为编译器优化使用了移位而不是除法器
网友评论:e...很少用到除法,2,4,8这些一般都是移位代替,效率要高一些。
网友评论:不懂,求高手解答
网友评论:t.jm: 其实全整数运算需要很多技巧,而且必须对cpu以及编译器有足够的了解,否则就会出现什么 “诡异”,“bug"之类的困惑。
除数是变量时,我想不会有问题,因为编译器一般会使用除法器。对于除数是常数,那么"什么 ...
highgear 发表于 2011-9-27 21:02
问题的焦点就不同的写法要结果一致,程序怎么写,只要合法就行了,何况这是常见写法呢
网友评论:t.jm: 其实全整数运算需要很多技巧,而且必须对cpu以及编译器有足够的了解,否则就会出现什么 “诡异”,“bug"之类的困惑。
除数是变量时,我想不会有问题,因为编译器一般会使用除法器。对于除数是常数,那么"什么 ...
highgear 发表于 2011-9-27 21:02
我只能告诉你,你刚才说的这些“技巧”我全知道!
用>>代替除法,你以为(-5)/ 4!= (-5) >> 2 ???
你说的这个“技巧”根本就无助与解决这个BUG!不管你是用/还是用>>:
1)|(-5)/ 4 | > |(-5) / 3|;
2)|(-5)>> 2 | > |(-5) / 3|; 你会选这种结构吧(>>)??
网友评论:t.jm: 其实争论这个情况是不是个 bug, 没有什么结果,因为判据不一致。对于我来说,我预期 x/4 为 x >> 2, 所以我不会认为 x/4 = -2是一个 bug,这也是我即使在 vc++下也不使用 x/4而是用 x>>2 的原因,因为vc++下的 x/4 会+3做调整,即 x/4底层为 (x+(x<0?3:0))>>2, 这会导致vc++ 下的算法程序直接移植后,结果可能不一致。
是不是个 bug, 痣者见痣,忍者见忍。
网友评论:t.jm: 其实争论这个情况是不是个 bug, 没有什么结果,因为判据不一致。对于我来说,我预期 x/4 为 x >> 2, 所以我不会认为 x/4 = -2是一个 bug,这也是我即使在 vc++下也不使用 x/4而是用 x>>2 的原因,因为vc++下的 ...
highgear 发表于 2011-9-28 09:59
你这样的考虑还怎么: 痣者见痣,忍者见忍???
/,>>这些技巧在IAR面前都是浮云,IAR会做出正确而高效的优化!
1)是不是BUG | -5 / 4| > |-5>>2|是铁证!
2)是不是优化,明显地IAR的对比可以让keil无地自容,两个常数的运算居然没有让编译器去完成还谈什么优化?
x = -5/4,应该直接优化为 x = -1,
x = -5 >> 2 应该直接优化为 x = -2!
网友评论:=-1,=-2确实都是正确的,因为C语言也是这样规定的
但同一个版本的编译器,不同的写法,不同的结果这就不对了,因为不同的写法本质是一样的,根本不可能有其它的解释
网友评论:=-1,=-2确实都是正确的,因为C语言也是这样规定的
但同一个版本的编译器,不同的写法,不同的结果这就不对了,因为不同的写法本质是一样的,根本不可能有其它的解释 ...
ayb_ice 发表于 2011-9-28 10:36
这个意思我早就表达了,
不管编译器(keil.IAR)它采用什么策略去计算 -5/4,-5/3,一致性它必须要遵守的,舍入的方向必须一致,-5/4 = -2,或=-1无非是精度问题,这不致命!
|-5/4|>|-5/3|在同一个软件中出现就是致命问题,藐视它,它就有可能是导致动车追尾的因素!
网友评论:IAR 我不知道,楼主的情况是不是bug尚有争议;但 t.jm 说keil“两个常数的运算居然没有让编译器去完成还谈什么优化”,显然是臆想。
网友评论:这个应该是在意料之中的事情,编译器为了高效使用了 RLC 指令
这个经常导致算法不稳定,有时甚至无法收敛.
网友评论:其实不是keil的错。
以8086为例:你用DIV,还是IDIV,同样的 -5/4,将会得到2个结果。关键是。2个结果都是对的: 注意商不一样,余数就不一样!
-5/4= -1……-1;
-5/4= -2……+3;
LZ的题目应写成下面,告诉编译器你要进行有符号除法而不是无符号除法运算:
main(void )
{
signedcharx,y,z;
z=-5;;
x=z/(signed)4; // 或者x=(signed) z/4;
y=z%4;
while(1);
}
网友评论:
上面带符号除法运算,C51调用专用除法库函数,余数在B。
无符号除法才用逻辑移位。否则符号位怎么处理?
网友评论:常数/非int型变量前加强制转换:(int)
网友评论:我也有碰到过哎
网友评论:搞编程的对BUG到底有没有清晰的定义?
我问你们:哪个BUG不是超出设计者预期?
又比如windows的BUG会被病毒设计者利用,你不利用它这个BUG又有什么伤害?你不利用别人会利用啊!
你不做/4运算别人有可能会用啊!
依次展开想象,今天你们用的有效计算方法,哪天就可能失效的,比如一再被大家提到的:
int/4,这只取决于,keil有没有兴趣把它优化为移位运算!你们所附加的强制类型转换会不会出错也取决于keil
会不会自作聪明的优化!
网友评论:在9.03上实验了一下,的确如楼主所说,我们可以认为这是一个bug。
问题的根源就在于对于字符型变量除以2的整数幂时,编译器会按照右移位的方式进行“快捷”运算,这样做的后果便是对有符号数的舍入方向将不一致。
而取余运算、以及非单字节运算,编译器都会调用库函数,因此不会存在这种问题。
楼主不介意的话,我去把这个bug向官方反馈一下,免得今后害人。
网友评论:其实不是keil的错。
以8086为例:你用DIV,还是IDIV,同样的 -5/4,将会得到2个结果。关键是。2个结果都是对的: 注意商不一样,余数就不一样!
-5/4= -1……-1;
-5/4= -2……+3;
LZ的题目应写成下面,告诉编译 ...
刘前辈 发表于 2011-9-28 18:44
改成
"x=z/(signed)4"
实际是等于
"x=z/(signed int)4"
这时结果当然对了,一个是char型除法了,一个是整型除法了
真正的BUG就是在此了
-5和4没有超出signed char的数据表示范围,用char除法和int除法应该结果是一样的才对
网友评论:在9.03上实验了一下,的确如楼主所说,我们可以认为这是一个bug。
问题的根源就在于对于字符型变量除以2的整数幂时,编译器会按照右移位的方式进行“快捷”运算,这样做的后果便是对有符号数的舍入方向将不一致。
而 ...
ejack 发表于 2011-9-29 08:14
这才是一个好人啊!
问题的根源大家都是知道的,就是移位代替真正除法产生的舍入方向不一致问题!
假如库函数的除法移位除法也是同一个舍入方向就没问题了,也就不再依赖于所谓的技巧:"/ >> int ..."
但是居然大多数电工都表现出:这就不是BUG,就如动车相撞、地铁相撞关我们电工屁事啊!!
网友评论:如果用移位代替有信号除法的话
结果至少是-1(被除数是负数的情况下)
网友评论:如果一个软件造成了飞机爆炸,动车相撞的事故,那一定是 人 造成的,而不是编译器。把事故归咎于编译器与铁道部的可耻言论没有什么区别。
网友评论:记得有位编程大佬说“优化会产生不确定性”,看来有一定道理。
网友评论:以前有个程序是在很老的版本下编译的,十几年来一直都很稳定,后来用最新版本又重新编译了,结果就不稳定了,开始是以为干扰问题,后来发现是优化问题,新版本比老版本优化不一样,过分智能了。
网友评论:如果一个软件造成了飞机爆炸,动车相撞的事故,那一定是 人 造成的,而不是编译器。把事故归咎于编译器与铁道部的可耻言论没有什么区别。
highgear 发表于 2011-9-29 08:57
这个话有些武断了
编译器也是人做的,有BUG不稀奇,如果是商业编译器,花了钱,是可以向厂家索赔的,这个没有问题的
测试的人也不能保证完全测试OK,当你知道有隐患的话,问题好解决,关键有些隐患不太明显,很难知道,当年INTEL的CPU有浮点运算错误,那你能说是微软不行,用户不行吗
网友评论:以前有个程序是在很老的版本下编译的,十几年来一直都很稳定,后来用最新版本又重新编译了,结果就不稳定了,开始是以为干扰问题,后来发现是优化问题,新版本比老版本优化不一样,过分智能了。 ...
yhn1973 发表于 2011-9-29 09:14
所以我说了,今天int/4是正确的,不代表将来也正确,发现BUG了不抓被咬一口就晚了!
而人啊,似乎是被咬了一口的BUG才算BUG,没咬过自己的不算!
网友评论:呵呵~~~
LS各位高手争论异常激励,有从C定义出发,认定这不能算是一个 bug, 只是超出人们的预期而已。
也有的人一口咬定这是一个bug,不过,不管怎么样,LS的盆友们都想出了名种方法,希望结果能得到人们预期的那样,但大多数盆友一般是将 /2或 /4转换成 int类型,以避开 keil对 char型变量的优化。
但是,将数据类型转换成 int类型,运算太费时间,程序也比原 char型运算长许多,并不理想~~~
这里向大家推荐两种常用方法,概避开了 keil对 /2或 /4char型变量的优化,又使用char型运算,运算时间仅比原来理想中增加几个时钟周期,程序也仅比原来增加几个字节,属于比较理想的打补丁,供大家参考~~~
方法1(ayb_ice推荐,特点是直观,但费时和占程序容量略多几个字节)
signed char x,z,a;
z = -5;
a = 4;
x = z/a;
方法2(俺推荐,用负数做除数,再对结果求补,特点是速度更快,费时更少,仅增加程序容量2个字节)
signed char x,z;
z = -5;
x = -z/-4; // 或写成 x = -(z/-4);效果一样。
网友评论:赞成楼主,确实是个BUG!因为z/3,z/5 都能正确调用CDIV (不是IDIV)字节除法库函数。
想了个办法,终于把z/4 能避开编译器移位BUG而用CDIV 函数计算了。
#include<REG52.h>
#include<math.h>
main(void )
{
signedchar x, y,z;
z=-5;
x=z/cabs(4);
y=z%4;
while(1);
}
这里有个基本概念: 2个二进制数的除法A/B,首先要符号位一致!然后才能进行运算。
所以一个负数z/+4,根本不可能用移位>>2来得到正确值;除非先把z转换为正整数cabs(z)。
x=cabs(z)/4; 也是对的。
网友评论:无论C?SCDIV (char 字节除法库函数)还是 C?SIDIV (int 除法库函数),首先都要将除数、被除数转化为符号一致的2个数。
网友评论:74#
方法2(俺推荐,用负数做除数,再对结果求补,特点是速度更快,费时更少,仅增加程序容量2个字节)
signed char x,z;
z = -5;
x = -z/-4; // 或写成 x = -(z/-4);效果一样。
千万别推荐方法2。x = -z/-4;和x = -(z/-4); 结果不一样!无论是手算还是程序算:
1、x = -z/-4 = 5 /-4 = -1 余 +1 ;
2、x = -(z/-4)= - ( 5 / 4) = -1余 -1 ;
、、
网友评论:这是向上舍入,还是向下舍入的问题, 与 c 语言无关。除法与算数移位的舍入方向不同,所以负数会产生不同结果。在数字信号处理中,对于算数移位操作通常需要加 0.5 补偿, 即 :
(x + (1<<(N-1))) >> N
这样可以防止算数移位向下舍入带来的误差。例如: -1 /32768,结果为 0, 但 -1 >> 15 = 0xFFFF>> 15 = 0xFFFF = -1, 即有一个相当大的舍入误差。对微弱信号处理时,可以明显地看到加 0.5 补偿后的效果。
网友评论:Keil 论坛上也有类似的讨论:
http://www.keil.com/forum/19617/
以及这一个:
http://www.keil.com/forum/14461/
No, it's not a bug at all.
If you get out your copy of K&R and look at the chapter about arithmetic operators, it say that the direction of truncation for the division operator is machine-dependent for negative operands.
This means that the result of (-1 / 2) can be 0 or -1 depending on the hardware and/or the compiler. Neither of the results would be considered an error of the compiler.
If you need a certain direction when truncating the result of dividing a negative number, you will have to do so by hand.
As already noted, the compiler can do whatever it likes for the division. The library function is well-defined - but there is no requirement for it to be the same!
网友评论:Keil 论坛上也有类似的讨论:
的确编译器拥有选择向零舍入或离零舍入的自由,但至少自己应该统一方向。
一会儿这样、一会儿那样,你能认为这种设计不存在问题吗?你能说这不是一个bug吗?除非Keil公开声明:“大家别用有符号整型做除法,俺不保证后果……”
网友评论:
79# 那两个帖子也能算讨论?就几个人说了几句话,正反1:1,说的都不到这里的1%,另一个明显是我们这里的ejack去报告的!
已经说过是不是BUG不是那么判地,精度问题可以接受,逻辑错误问题是不能接受的!
假如:
signed char ad1,ad2, x,y;
ad1 = -5;
ad2 = -5;
x = ad1/4;
y = ad2/3;
if(x > y){
....
}
else{
....
}
网友评论:作为技术人员应具备严谨的精神和理性的态度。对于负整数除法,我比较了 c89 与 c99 标准,c89 并未规定舍入方向,而 c99 规定了 truncate to zero. 如果 keil c 声称遵循 c99, 则向上舍入是一个bug, 否则,就不能说这是一个bug, 不论是否合乎人们的预期,也不宜凭个人感觉或是好恶。虽然可以指责keil违背一致性原则,而违背一致性原则有时比 bug 还恶劣。
Keil C 遵从 C90:
Keil C compilers are based on C90. We added some language extensions as practical concessions to the architectural peculiarities of the microcontrollers we support and the needs of embedded systems programmers.
t.jm 楼上的例子在 c89 之类的编译器下没有实际意义,因为编译器不保证负整数除法的舍入方向,这应是程序员自己来保证舍入。t.jm 坚持是一个bug, 那么请指出违背了标准中的哪一条原则。
网友评论:可以认为,不一致就是BUG
我还认为是严重BUG
网友评论:作为技术人员应具备严谨的精神和理性的态度。对于负整数除法,我比较了 c89 与 c99 标准,c89 并未规定舍入方向,而 c99 规定了 truncate to zero. 如果 keil c 声称遵循 c99, 则向上舍入是一个bug, 否则,就不能说 ...
highgear 发表于 2011-9-30 08:56
你还真能据“理”力辩呢!!
“虽然可以指责keil违背一致性原则,而违背一致性原则有时比 bug 还恶劣。”
你意思是说就像一个IC,你的使用超出我的承认书范围,出现任何问题我一概不负责???
这我不苟同!!!!
其它编译器又是以什么为标准?IAR8051,FSL CWS08,STVD,KEIL ARM,SDCC 51...怎么不见别人犯错?
网友评论:ayb_ice: 那么就拿出确实的证据出来,而不是“我认为”。 不一致性在 c#, java 语言中也都存在,例如java 中著名的字符串 == 比较问题,也不被认为是 "bug"。
t.jm: 首先, -5/4 = -2 不是错误。其次,如果ic的承认书范围没有违背法令或是行业标准,那么用户是应该为不当使用而负责。至于其他编译器,并不是标准,无从参照。