上程序:
#include "STC15W4K.H" // 注意宏定义语句后面无分号
void main()
{
unsigned char a=0; // a定义为无符号字符型(0-255),
char b=0; // b定义为带符号字符型(-128~127)
while(1)
{
a=-3; // 执行后 a=1111 1101,KEIL输出显示 253
// 分析:[-3]=[1000 0011]原=[1111 1100]反=[1111 1101]补
// 由于a定义为无符号字符型,[[1111 1101]补]补= 1111 1101=253
b=-3; // 执行后 b=1111 1101 KEIL输出显示 -3
a = 127; // 执行后 a=0111 1111 KEIL输出显示 127
b = 127; // 执行后 b=0111 1111 KEIL输出显示 127
a = a + 3; // 执行后 a=1000 0010 KEIL输出显示 130
b = b + 3; // 执行后 b=1000 0010 KEIL输出显示 -126
}
}
分析:记住一点,为了负数的计算方便,计算机对数的存储均是按照补码的形式存储的。单片机也是如此, 所以内存中运算就很方便了,直接按照无符号相加就行了。
正数的补码等于反码等于原码。
负数的补码等于原码取反再加1。
第一个结果 a = -3
-3 对应的原码为 1000 0011, 对应的补码为反码再加1(除去符号位),即是: 1111 1100 + 1 = 1111 1101 这就是-3在内存中的存储值。现在把它赋值给a,。也就是说a的补码现在就是1111 1101。至于对应的原码是多少,这之前编译器要看一下a是有符号数,还是无符号数。一看a是无符号数,那么最高位就无所谓了,数据位就是8位。那么好了,补码就等于原码。原码为1111 1101 就是253咯
注意:负数原码中最高位为符号位,数据位只有7位(2^7)。正数的数据位为8位(2^8)这也侧面反映了一点,正数的取值范围是 0~255,负数的取值范围为-128~127!
第二个结果b=-3
同时此时b在内存中对应的补码就是 1111 1101了,至于它对应的原码多少。还得看它的定义类型是啥。一看是char, 有符号,好了。那么对应最高位就是一个符号位了。求对应的原码方法就是除去最高位其他位再求补码。
1 000 0010 + 1 = 1 000 0011 显然就是-3咯。
第三个结果a=127
单纯就看127,为正数。在内存中的补码就等于原码为:0111 1111 也就是现在a对应内存的补码就是0111 1111了,再看一下a是无符号且是正数,那么好了。补码就是原码,a就是127了。
第四个结果b = 127
同上,b在内存中对应的补码为 0111 1111, 再看一下b是有符号,但是符号位为0,有符号char的取值范围是 -128~127没超过,那么也即是正数,所以补码等于原码,就是127了。
第五个结果a+=3;
127+3=130没超过无符号char的取值,所以结果就是130
第六个结果 b+=3
先大概一瞅肯定会溢出,至于结果是几,且来分析一般。
b对应补码为(这两补码相加即可) 0111 1111 + 0000 0011 = 1000 0010 这就是b现在对应的补码。且b为无符号数,最高位为1,负数。好了,原码等于补码的补码。即是 1 111 1101 + 1 = 1111 1110
表示为1 111 1110 = -126
总结一下规律, 先不用考虑左边对应变量的数据类型或者溢出什么的。先把右边对应的数的补码写出来(正数补码为自身,负数补码为反码加一)。然后再看对应的事有符号数还是无符号数。
两个特殊的0和128
正0的补码等于负0的补码等于0
但是-128的补码似乎没法表示了,因为它的原码和反码没法7位表示
可以这么想,-1是最大的负整数,-1的补码1111 1111减去1得到-2的补码
1111 1110,那么-127的补码 1000 0001减去1不就得到了-128的补码吗?!就是1000 0000
补充: 负数的补码快速运算为:符号位不变。其他的从低位开始,直到遇到第一个1之前,什么都不变。遇到第一个1后,保留这个1,以后按位取反。
[-7]原 = 1000 0111
[-7]补 = 1111 1001
数值在内存中确实是按补码形式存放的,数据长度超过最高有效位后,直接去掉最高有效位外的符号和数值。