终于明白 a = (168*196) / 10 ; 为什么诡异

[复制链接]
13252|55
 楼主| 刘前辈 发表于 2011-1-11 19:09 | 显示全部楼层 |阅读模式
本帖最后由 刘前辈 于 2011-1-17 09:57 编辑

TH0 = (- 5000 ) / 256 ;      // 为什么会等于0x ED !
a = (168*196) / 10 ;          // 为什么会等于0xF344!

    计算机程序混了这么多年,竟然今天才弄懂,惭愧。

      (-5000 )=0xec78 ;      
       在32位机器上表达为0xFFFFEC78,我希望的是低16位纯粹右移8位,然后取低8位,而不是要一个负数除法表达式。因此,我一定要保证是无符号数0x0000EC78 / 0x100 ;   所以应该写为:

    TH0 = (unsigned (- 5000 ) / 256 ;   
                                //  不是强制类型 uint  什么的。概念应该不一样


而所谓诡异事件   a = (168*196) / 10 ;  应该写为:

    a = ( unsigned ) (168*196) / 10 ;        //  不是强制类型 (unsigned  int )

这样保证0x80A0不会表达为0xFFFF80A0,——  一个负数;造成结果诡异。


这是C语言标准中“整提升+保符号/ 保值 规则的问题。”——当一个16位的int 值最高位(符号位)为1时,它也许是表示一个负数,或许是一个经过编译器自动整提升进位上来的正数,例如:168*196= 0x80A0;  它是由int自动整提升为unsigned  int  的;符号位怎么办?——这就是整提升之后运用的保符号还是保值规则;有的编译器缺省使用保符号规则(默认丢掉的符号是1——负数标识),有些编译器缺省采用保值规则 (忽略丢掉的符号,默认为0——正数标识),还有些编译器更完善,可以由用户使用编译开关来选择保值还是保符号规则。举例:0xFF, 由于符号位为1,所以既可以翻译为255d (保值规则),又可以翻译为 -1d(保符号规则),两者都是对的。究竟采用什么规则,由用户决定。
        像程序医人的例子,如果希望得到正数,就可以采用采用强制unsigned 告诉编译器采用保值规则;否则,希望得到负数,就采用默认(ANSI   C 默认保符号规则。),这就是为什么168*196得到一个负数的原因,也是我上面加入 unsigned  的理由。


NE5532 发表于 2011-1-11 19:36 | 显示全部楼层
引用谭浩强前辈的一句话“不要写出自己都不知道计算机会如何运行的程序”
ycz9999 发表于 2011-1-11 20:02 | 显示全部楼层
学习下……
程序医人 发表于 2011-1-11 20:44 | 显示全部楼层
不通也,请前辈指点明白点!
highgear 发表于 2011-1-11 22:26 | 显示全部楼层
“VC是16位基本单元的“, 这是想当然的胡扯。

编译器默认整数为 int 类型, 而不同的编译器对待 int 也不同, vc++ 中的 int 为32bit, Borland C++以及大多数嵌入式编译器 int 为 16bit,  做算术运算, 一定要对数制运算转换有清晰的概念, 要注意正整数的溢出问题, 很多普通 cpu 没有自动防止溢出的功能。

168*196 超过了16 bit 正整数 (32767)的上限而变为负数: -32608, 并没有超过32bit 正整数 (0x7FFFFFFF) 的上限.

要注意编译器在计算=右边的表达式时不会理会 =左边的数据类型, 只有在最后赋值时才会做类型转换.
所以,最好人工指定表达式的类型, 也可使用 L, ul, f 等数据后缀, 清晰而不易出错。
highgear 发表于 2011-1-11 22:26 | 显示全部楼层
“VC是16位基本单元的“, 这是想当然的胡扯。

编译器默认整数为 int 类型, 而不同的编译器对待 int 也不同, vc++ 中的 int 为32bit, Borland C++以及大多数嵌入式编译器 int 为 16bit,  做算术运算, 一定要对数制运算转换有清晰的概念, 要注意正整数的溢出问题, 很多普通 cpu 没有自动防止溢出的功能。

168*196 超过了16 bit 正整数 (32767)的上限而变为负数: -32608, 并没有超过32bit 正整数 (0x7FFFFFFF) 的上限.

要注意编译器在计算=右边的表达式时不会理会 =左边的数据类型, 只有在最后赋值时才会做类型转换.
所以,最好人工指定表达式的类型, 也可使用 L, ul, f 等数据后缀, 清晰而不易出错。
STM32W108 发表于 2011-1-12 08:21 | 显示全部楼层
第二个问题,C语言基本功。

unsigned int a;   
a=(168*196)/10;  

为什么结果是62276?
为了解答这个问题,我们先看下面几个问题。

1、168,196,10是什么类型??
答案:int。根据C语言标准,字面值整型常量,10进制且没有尾缀,能够在int表示的,都是int型。

2、int型与int型运算,包括+-*/,结果是什么类型?
答案:int。根据C语言标准,int与int进行算术运算,结果类型还是int。

3、如果int是16位,168*196结果是什么类型,结果值是多少?
答案:结果类型是int,值是-32608,16进制表示0x80A0。

4、-32608/10结果类型是什么?值是多少?
答案:结果类型是int,值是-3260,16进制表示0xF344。

5、unsigned int a; a=-3260;
结果a的值是多少?
答案:a的值是62276.
batsong 发表于 2011-1-12 08:37 | 显示全部楼层
立即数必须指定类型啊,不然会出现很多诡异现象
 楼主| 刘前辈 发表于 2011-1-12 10:22 | 显示全部楼层

大侠highgear说0xEC78/ 0x100没有确定解?

本帖最后由 刘前辈 于 2011-1-12 10:25 编辑

建议highgear 大侠看看7楼在说什么。——C语言基本功。

highgear 大侠认为这与C语言无关,是我们初学者都不懂的另一方面的问题,他的独立唯一见解:
     ——这属于“四舍五入的问题,弱信号处理,误差±0.5……与C语言无关。”

确实深奥,毕竟是论坛上资历最深的大师。

 楼主| 刘前辈 发表于 2011-1-12 11:05 | 显示全部楼层

highgear大师不仅懂C51,而且懂 VC++ / BC++

本帖最后由 刘前辈 于 2011-1-12 11:22 编辑
5楼
“VC是16位基本单元的“, 这是想当然的胡扯。VC++……BC++……


5楼大侠是不是应该举个VC编译器视 int 为32位或者64位的好一点?否则,你在上层,说下层是胡扯,你玩32位机视我们玩8位机的这个论题是菜鸟胡扯,这不合逻辑呀。讨论是在对等的平台上进行的。——之所以说诡异事件,完全是因为在8位处理器编译器的基础上。你玩PC机的当然没这个诡异。当然视此为想当然的胡扯。显得高明吗?
         否则,前辈要这么说了:“很多CPU是64位的,也许还有什么VC++++(VC#)编译器视int 为64位,那么:
168*196=0x00000000000000A8*0x000000000000000C4=0x000000……80A0;

也许还有哪位知道什么VC+++的视int为32位或者128位的,比前辈和highgear更高明?

coody 发表于 2011-1-12 11:43 | 显示全部楼层
LZ,这个不关8位机的事,8位机表示鸭梨很大。。。。
那是编译器的事。
coody 发表于 2011-1-12 11:46 | 显示全部楼层
另外,LZ真的不明白C语言中数的表示方法?
其实不需要a = ( unsigned ) (168*196) / 10 ;
我一般写成 a = (168L * 196) / 10 ;
datouyuan 发表于 2011-1-12 12:09 | 显示全部楼层
这种问题值得讨论。

我以前也碰到类似问题,搞得我很烦。

我现在一般每个数都要指定类型,然后整个表达式也指定。

#define FREQ_LO ((uint16)(1538000/10/TICKS_PER_SEC))        //1.538MHz
#define FREQ_HI ((uint16)(2080000/10/TICKS_PER_SEC))        //2.08MHz
//#define CUR_WHITE_LO ((uint16)(211ul*4096ul/5000ul))                //(45mA*4.7ohm)
#define CUR_WHITE_LO ((uint16)(202ul*4096ul/5000ul))                //(43mA*4.7ohm)
#define CUR_WHITE_HI ((uint16)(259ul*4096ul/5000ul))                //(55mA*4.7ohm)
//#define CUR_RED_LO ((uint16)(1700ul*4096ul/5000ul))                        //(25mA*68ohm)
#define CUR_RED_LO ((uint16)(1564ul*4096ul/5000ul))                        //(23mA*68ohm)
#define CUR_RED_HI ((uint16)(2380ul*4096ul/5000ul))                        //(35mA*68ohm)
//#define CUR_BLUE_LO ((uint16)(975ul*4096ul/5000ul))                        //(25mA*39ohm)798        //
#define CUR_BLUE_LO ((uint16)(897ul*4096ul/5000ul))                        //(23mA*39ohm)798        //
#define CUR_BLUE_HI ((uint16)(1365ul*4096ul/5000ul))                //(35mA*39ohm)1118//
//#define CUR_GREEN_LO ((uint16)(750ul*4096ul/5000ul))                //(25mA*30ohm)
#define CUR_GREEN_LO ((uint16)(690ul*4096ul/5000ul))                //(23mA*30ohm)
#define CUR_GREEN_HI ((uint16)(1050ul*4096ul/5000ul))                //(35mA*30ohm)
zuiyedemeng 发表于 2011-1-12 13:42 | 显示全部楼层
程序医人 发表于 2011-1-12 14:13 | 显示全部楼层
立即数必须指定类型啊,不然会出现很多诡异现象
batsong 发表于 2011-1-12 08:37


学习了,,谢谢!
johnwjl 发表于 2011-1-12 17:27 | 显示全部楼层
这就是隐形转换的问题,请参考《C专家编程》。
程序医人 发表于 2011-1-12 21:47 | 显示全部楼层
这个《C专家编程》是哪路人马写的啊,我感觉这个老外写的书就是怪怪的,看上去不知他要说什么,好像条理不清晰,就是不喜欢看外国书。也许是文化有差异思维方式也不同吧。
highgear 发表于 2011-1-12 22:21 | 显示全部楼层
刘公, 给你说了无数遍了, 不懂不要装懂。

谁告诉你 “0xEC78/ 0x100没有确定解?“ 大家已经讲的这么清楚了, 你的脑袋怎么还不明白?

“VC编译器视 int 为32位或者64位的好一点?“  vc++中的int 是32bit, 这点毋庸置疑, 你刘公公肯定没有用过 vc++, 这并没有妨碍你装做懂vc的样子, 就像以前装做懂bios一样. 不错, 我不仅懂c51, 还懂vc++, bc++, 甚至c#, 这你刘公公不必装聋作哑, 以前的 PK 中你刘公公不是没有见识过。 未来的64bit机上的 c++,  int 极有可能变为 64bit. c 标准中没有规定 int 的位数, 这不是疏忽, 而是有特定理由的。

刘公, 再给你说了一遍, 不懂不要装懂。 -5000>>8 涉及逻辑还是算术移位的问题, -5000/256涉及到整数除法的问题, 两个算式的舍入方向不同, 造成 1 的差别, 与 c 无关, ceiling 与 floor 的问题在其他语言也都存在, 包括汇编, 这是cpu本身处理的问题。 去想想 -1 >> 16 的结果, 这显然比挂一幅画简单, 不用找人帮忙。
highgear 发表于 2011-1-12 22:46 | 显示全部楼层
算了, 不说刘工了。
(168*196) / 10, 已经很清楚了。我把 -5000>>8, 这个问题讲明白。
cpu 中通常有逻辑移位与算术移位, 但是 c/c++ 里很奇怪, 没有规定 >> 到底用的是什么。而 java 里明确规定了逻辑移位与算术移位 >>, >>>. microsoft vc++ 里规定负整数移位使用算术移位, 这也是很多其他编译器的做法。算术移位是向下舍入, -5000 >> 8 = 0xFEC78 >> 8 = 0xFFEC = -20; 而 -5000/256 整数除法, 实际上是 5000/256 后加符号, 即:整数除法 -5000/256 = -(5000/256) = - (19) = -19 = 0xFFED. 正整数除法也是向下舍入, 但取负后变成向上舍入。
efen 发表于 2011-1-13 19:42 | 显示全部楼层
这个还的真要留意,学习了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:做自己所热爱的,并热爱自己所做的。

24

主题

1038

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部