打印

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

[复制链接]
10085|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 | 只看该作者
不通也,请前辈指点明白点!

使用特权

评论回复
5
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 等数据后缀, 清晰而不易出错。

使用特权

评论回复
6
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 等数据后缀, 清晰而不易出错。

使用特权

评论回复
7
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.

使用特权

评论回复
8
batsong| | 2011-1-12 08:37 | 只看该作者
立即数必须指定类型啊,不然会出现很多诡异现象

使用特权

评论回复
9
刘前辈|  楼主 | 2011-1-12 10:22 | 只看该作者

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

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

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

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

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

使用特权

评论回复
10
刘前辈|  楼主 | 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更高明?

使用特权

评论回复
11
coody| | 2011-1-12 11:43 | 只看该作者
LZ,这个不关8位机的事,8位机表示鸭梨很大。。。。
那是编译器的事。

使用特权

评论回复
12
coody| | 2011-1-12 11:46 | 只看该作者
另外,LZ真的不明白C语言中数的表示方法?
其实不需要a = ( unsigned ) (168*196) / 10 ;
我一般写成 a = (168L * 196) / 10 ;

使用特权

评论回复
13
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)

使用特权

评论回复
14
zuiyedemeng| | 2011-1-12 13:42 | 只看该作者

使用特权

评论回复
15
程序医人| | 2011-1-12 14:13 | 只看该作者
立即数必须指定类型啊,不然会出现很多诡异现象
batsong 发表于 2011-1-12 08:37


学习了,,谢谢!

使用特权

评论回复
16
johnwjl| | 2011-1-12 17:27 | 只看该作者
这就是隐形转换的问题,请参考《C专家编程》。

使用特权

评论回复
17
程序医人| | 2011-1-12 21:47 | 只看该作者
这个《C专家编程》是哪路人马写的啊,我感觉这个老外写的书就是怪怪的,看上去不知他要说什么,好像条理不清晰,就是不喜欢看外国书。也许是文化有差异思维方式也不同吧。

使用特权

评论回复
18
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 的结果, 这显然比挂一幅画简单, 不用找人帮忙。

使用特权

评论回复
19
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. 正整数除法也是向下舍入, 但取负后变成向上舍入。

使用特权

评论回复
20
efen| | 2011-1-13 19:42 | 只看该作者
这个还的真要留意,学习了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

24

主题

1038

帖子

4

粉丝