[部分原创]关于原反补移要点的总结(二)
由-128的补码引出的深层次思考。
来源:http://blog.csdn.net/band_of_brothers/archive/2008/07/04/2612460.aspx
点评:比较新颖的思路,自己不太懂,算是了解一下吧。目前“根据上面的说法,分析下c中具体的问题”,这个只是用计算器算了一下,16进制的码和二进制差不多,f是最大,0是最小而已。
由-128的补码引出的深层次思考。点评:比较新颖的思路,自己不太懂,算是了解一下吧。目前“根据上面的说法,分析下c中具体的问题”。这个还是不太懂。一会看看是怎么计算的。
一般的说法是负数的补码为其原码除符号位外取反然后总体加一,也就是说,要得到一个负数数的补码,要先知道这个负数的原码才行。那么,问题出现了,在8位长度下,-128的原码与反码都不存在,因为一个字节的有符号数的原码范围是: -127 ~ + 127 ,既然不存在 -128的原码那么就无法求出 -128 的补码了,怎么办?
其实,这个问题的实际意义是,既然说计算机内部的有符号整数都是补码,那么怎么才能有效的实现这一设计呢?潜台词是:根据上面由原码推导出补码的理论,如果是正数,计算机得到其原码,也就是得到了其补码(正数补码等同于原码),如果是负数,先得到其原码,然后再取反加一就可以了。也就是说按这个思维设计编译器,或计算机电路,就可以了。但是如果出现开始说的 -128的补码问题,这个设计就不能工作了。其实,在真正的设计中,这种获得负数补码的“取反加一”的方案根本没有实施过!
拿现代的计算机举例,输入设备只有键盘,通过编辑器程序把键盘的扫描码变成ascii码存储起来,比如输入 ‘a’就存储 0x61 ,输入 ‘5’就存储 0x35 ,这些都是ascii码。当你想得到真正的机器数时(注意,机器数和ascii是不同的,字符‘5’的ascii码是0x35,而数字5的机器数是 0x5),需要借助编译器把表示数字的字符串从ascii码变成真正的机器数,比如你想得到 56的机器数(就是0x38),就可以输入语句“ db 56 ”,让汇编器程序帮你把56的ascii码字符串 :“0x35, 0x36”,转变成真正的机器数 0x38。这一转化需要这样一段程序:先把 ‘5’与 ‘0’做减法,就是 0x35 – 0x30 得到 0x5 (这一步就将ascii字符5变成了真正的机器数5),再把 0x5 与 0xa (就是十进制 10)相乘得到 0x32 (就是十进制 50) ,然后再把 ‘6’与 ‘0’做减法,就是 0x36 – 0x30 得到 0x6 (就是机器数6),最后把 0x32 与 0x6 相加,得到 0x38 ,就是机器数56了。
如果想得到 -56 ,就用语句 “ db -56 ”,这里得到机器数 56 的步骤与上面一致,只是最后要把 0xff 与 0x38 相乘,就是 -1 * 56 ,最后得到 0xc8 ,就是 -56 的补码。
这里我们看到,得到 -56 得补码时根本没用什么“取反加一”,这里处理的过程都是很自然的,只要考虑各个数值的运算,而不用考虑数值的补码形式,以及如何得到负数的补码。为什么?因为加法,有符号乘法等指令的电路,都是按补码输入,补码输出来设计的,你只要保证输入的是补码,输出的肯定也是补码。所以,只要你输入 -1 的补码 0xff ,与56的补码 0x38 ,得到的自然是 -56 的补码 0x c8 。综上,我们在获得 -56 的补码时,没有采取先得到 -56 的原码,然后除符号位外各位取反,最后再总体加一的方式。
由此可见,计算机是一个相当“封闭”的系统,他内部所有的有符号整数都是补码形式存在的,只要按数值的实际意义考虑问题,不用担心它的存储方式,比如想让 -56 与 6 相乘,你根本不用担心结果那个负数怎么变成补码,所有的运算电路都是按补码设计的。换句话说,“封闭”的计算机内部的有符号数都是补码的形式存在的,你根本不用考虑什么原码,什么取反加一了,你只要考虑你想要的数值就可以了,不要担心他怎么存储的。
有了上面那个ascii码到机器数的转换程序后,数字的输入就不再是问题了,但是,考虑的更远一点,如果在计算机的“蛮荒时代”,所有的指令,数据,都要用机器语言一位一位的输入时(搬开关、打孔),那时的有符号整数又怎么输入呢?确实,在那个环境下,就只能用我们的大脑计算了,把数字在大脑中转换成补码的样子,然后输入。其实,真正需要我们在大脑里转换然后再输入的数只有5个,分别是 ‘+’,‘-’,‘0’,0,-1 ,他们的补码分别是:0x2b ,0x2d,0x30,0x00,0xff ,好了,用这5个补码和机器指令编出我们刚才讲的ascii码转化机器数的程序,从此以后,我们只要输入数字的ascii字符串就可以了,让这个程序帮我们转换成补码,不用再辛苦的计算补码了。
深入到机器层,机器层面也根本用不着“原码取反加一”,因为机器里的所有数字,都是人工或者是编译器输入的,已经转换成了补码了,他本身已经是一个完备而封闭的系统了,根本没有接口接收其他有符整数的编码方案了。需要注意的是有一个取补指令neg ,这里的取补不是本来意义的“取反加一”(本来意义的“取反加一”只对负数),而是不论正负,把每一位取反,最后加一,就是说 neg 20 结果为 -20 ,neg -56 结果为56 ,就相当于把操作数与 -1 像乘了。这显然与为得到负数的补码采取的“取反加一”截然不同。当两个数做减法时,比如:有符号整数 60 (0x3c) 与 有符号整数 77 (0x4d)相减,加法器有一段电路把减数77取反加一,但是,请注意,这个电路跟neg一样,不管正负每位取反最后加一,就相当于用 乘法指令imul 把 操作数 与 -1 相乘了。这也跟求负数的补码采取的“取反加一”截然不同。
综上,无论在编译器层面还是机器层面,“负数的补码为其原码除符号位外取反然后总体加一”这个方法都没有用上,这只是教科书上提供的便于记忆的方法而已。
根据上面的说法,分析下c中具体的问题: c中int占4个字节,表示范围从 – 2147483648 到 2147483647 。
问题1:int -2147483648 ;编译后的值正确吗?
上面说的转换程序,这个编译的步骤为: 计算2*10^9 + 2*10^8+ …… +4*10+8 ,因为int只能表示到 2147483647 (0x7fff ffff),再加1的话就溢出了,得到了:0x8000 0000 ,(就是 -2147483648) ,然后再把这个结果与 -1 就是 0xffff ffff 相乘,得到的结果也溢出了,为:0x8000 0000 ,但是这恰好是 – 2147483648的补码,所以,虽然编译中虽然出现了两次溢出,但得到的结果是正确的。
问题2:int x = - 2147483648; 那么 –x 是 2147483648吗?
因为 – 2147483648 (0x8000 0000)与-1 (0xfff ffff)相乘结果为:0x8000 0000 就是– 2147483648 ,所以 –x 的结果还是 – 2147483648 而不是2147483648 。 所以,凡是结果可能超越表示范围的时候,一定要慎重,因为结果可能是你意料之外的。
原码、补码和反码
来源:http://blog.chinaunix.net/u/20/showart_438418.html
点评:引用一个评论“
一般人讲补码,先引入原码,讲如何从原码转成补码,教运算;高级点的人讲补码,从代数原理的角度来论证,谈合理性;而博主,从离散数学的角度来谈这个问题,还谈得如此地道,实在是令在下佩服!”反码后部分没怎么看懂,继续研究中。
在计算机里如何表示整数?
整数有无穷多个,在计算机里,通常我们只能表示出其中的一部分。假如我们用 n 个比特来表示一个整数。1 个比特有 2 个状态,n 个比特就有 2^n 个状态,把这 2^n 个状态的集合记为 A. 显然,用 A,我们可以与 n 个整数建立起一一对应。我们还希望 A 所表示的整数能够象整数那样地运算---整数,象整数那样运算,这是不是一句废话?数学中的整数相加,仍然是一个整数,但 A 里两个整数相加,我们却无法保证它们的和仍在 A 中,用代数的术语来讲,叫做 “不满足封闭性”,这是个很坏的性质。
一、补码
不过数学上有处理这个问题的成熟方案,如果我们能后退一步,让 A 表示的是模 |A| 的剩余类,则加法运算马上就封闭了。而且这个时候 A 不仅可以与 2^n 个整数对应起来,而且,在某种意义下,可以与整数环 Z 对应起来。用代数的观点,这个 “某种意义”就是所谓的同态。
整数有两种封闭运算,一种是加法,另一种是乘法。A 作为模 2^n 的剩余类,也有加乘两种运算。定义 Z 到 A 的映射
f(x) = m mod 2^n
f 是一个同态,也就是说,f 满足这样的良好性质:
f(x+y) = f(x) + f(y)
f(xy) = f(x)f(y)
我们通常使用 10 进制数,在这个进制下,f(x) 并不容易计算,但是在计算机里,本质的表示是二进制,于是 f(x) 的运算变得出奇地简单。如果 x 小于 2^n,则 x 的 2 进制表示就是 f(x),如果 x>=2^n,则要求其模 2^n 的余数,这恰好是 x 二进制表示的最低 n 位,换句话说,简单地把高位抛弃就行了。顺便指出,f(0)=0, f(1)=1.
我们来看一看 A 中的加法,f(x)+f(y), 若结果小于 2^n,则运算自然封闭,如果 f(x)+f(y) >= 2^n,则取其最低的 n 位,用电路实现时,可以简单地扔掉高位,保留低位。
到目前为止,一切都很好,但是减法怎么办呢?对整数运算而言,减去 a 不过是加上 a 的相反数的同义语。只要对 A 中的每个元素,能容易计算出其相反数就可以了。理论上 f(x) 的相反数就是
f(-f(x))
不过这个好像不容易计算,因为我们现在并没有给出 A 中“负数”的概念,事实上 A 是模 2^n 的剩余类环,根本就没有所谓的负数。
这个困难也是容易处理的。作为 f(x) 的相反数,-f(x) 应该满足这样的性质:
-f(x) + f(x) = f(0) = 0
所以我们只要有在 A 中找一个元素,使得它与 f(x) 的和是 0 就可以了。但是 f(x) 本身可能含有很多比特 1,加上一个数能使它们变成 0 吗? 考虑到 A 中的加法要模去一个 2^n,这个问题实际上很好办,只要让求出的和是 2^n 就可以了。所以难发现 -f(x) 就是把 f(x) 的比特“取反”---即 0 变 1, 1 变 0,并加上 1 就得到了 -f(x)。容易验证:
f(-x) = - f(x).
现在我们回过头来看前面的一句话,红色部分:
“我们通常使用 10 进制数,在这个进制下,f(x) 并不容易计算,但是在计算机里,本质的表示是二进制,于是 f(x) 的运算变得出奇地简单。如果 x 小于 2^n,则 x 的 2 进制表示就是 f(x),如果 x>=2^n,则要求其模 2^n 的余数,这恰好是 x 二进制表示的最低 n 位,换句话说,简单地把高位抛弃就行了。顺便指出,f(0)=0, f(1)=1.”
红色那句话简直是胡扯,因为没有考虑到负数的情况。但 f(x) 容易计算这句话并没有错,因为当 x 为负数时,我们可以利用 f(x) = -f(-x)。 由于 -x 是正的,f(-x) 容易计算,之后 -f(x) 也不过是取反加 1 而已。
好,到目前为止---在数学世界里,简直是完美的。但是回到现实, A 里头真的没有负数,怎么办?整数能比较大小,A 里头的数又怎么办?
这时候,我们可以用一个不太完美的方案,把 A 里头的元素再映射回整数 Z 中,如果只需要无符号的数,则变换为
g(u) = u, 0<= u <2^n
其实就是不把 A 中的元素模 2^n 的剩余类,而直接看成整数。不过这时的运算就要考虑溢出了。如果是无符号的情形,运算的结果仍是模 2^n 的余数,外加一个溢出标志。
如果要考虑负数,如果没有特别的理由,则要求正负个数大致相等是自然的。可以考虑让 0 代表 0, 1~2^{n-1}-1 代表正数,2^{n-1}+1~2^n-1 代表负数。丢掉一个 2^{n-1} 是因为希望正数和负数刚好配对,若 u 代表负数,则其落在 2^{n-1}+1~2^n-1 中,但它代表负几呢?当然是负 -u 了。从两个相反数之和为 0 ,或 2^n 的观点,-u 就是 2^n-u, 而 u 则是 -(-u) 即 u-2^n。
2^{n-1} 是个麻烦,在 A 中,它的相反数就是自己,把它当正数,负数都不大合理。出于完整性考虑,随便选一个好了。在这里我们让 2^{n-1} 为负数,理由是它的最高位是 1,跟其它“负数”长得比较象。
换个角度看,模 n 的剩余类,或者余数为 0,1,...,2^{n-1}, 2^{n-1}+1, ..., 2^n-1. 这些余数也可以表示为:
0,1~2^{n-1}, 2^n-(2^{n-1}-1), ..., 2^{n}-1
我们刚才所做的就是把上面数列中 2^{n} -r (1<= r<= 2^{n-1}-1)类型的的数对应为负数 -r 而已。用代数的术语,就是在陪集中选取了不同的代表元。
于是我们定义 A 到 Z 的变换,使得
当 0<= u <= 2^{n-1} 时, g(u) = u
当 2^{n-1} <u < 2^n 时,g(u)=u - 2^n
当然,这时运算起来有可能溢出,但结果仍可以看成模 2^n 的余数,外加一个溢出标记。
上面所说的就是补码。
二、反码
抽象地看,反码与补码只有一个区别,同样的 n 比特状态集合 A,反码让这些元素表示模 2^n-1 的剩余类,而补码模 2^n。于是从 Z 到 A 的映射定义为
f(x) = x mod 2^n-1
不过且慢,A 的大小是 2^n,而模 2^n-1 的余数只有 2^n-1 个? 是的,这是反码一个不完美的地方,它浪费了全 1 的码字,不过我们可以把全 1 的和全 0 的数看成一样的。
f 同样是个环同态,满足
f(x+y) = f(x) + f(y)
f(xy) = f(x)f(y)
俄且 f(0)=0, 或者全 1, f(1) = 1.
f(x) 可以计算如下,设
x = 2^n q + r,
则由于 2^n = 1 mod (2^n-1),所以
x = q + r (mod 2^n -1)
若然 q+r 仍大于等于 2^n,则递归使用上述步骤。当然也可以直接去模 2^n-1,但上面的处理充分利用了二进制表示。而且当 x 为负数时,我们有两外的处理方法,见后面相反数的部分。
f(x) 能求出后,A 中的加法运算和乘法运算也就容易处理了,若 f(x)+f(y)<2^n,则不作特别处理,如果 f(x)+f(y) >=2^n,则结果为 f(f(x)+f(y)) 即把第 n+1 比特加到最低比特上。对于乘法,f(x)f(y) 可以多出n-1 位,处理方法是把第 n+i 比特加到第 i 比特上,或者说把高出 n 位的比特都右移 n 位并加在低位上, 如果仍然越界,则重复上述步骤。从这里可以看到反码的一个弱点,它的越界处理比较麻烦,而补码直接把越界的位丢掉就是了。
A 中的相反数如何求? -f(x) + f(x) = f(0) = 0, 加出一个全零比较难办,但加出一个全 1 不是问题,所以我们可以让 -f(x) 为 x 的取反,即 0 变成 1, 1 变成 0。于是求相反数的问题解决了。
同样,我们可以考虑在反码中引入正数,负数,这个与补码的类似,这里就不多说了。
有趣的是 2^n 几乎不可能是素数,而 2^n-1 有可能. 2^n-1 形的素数称为梅森素数。如果 2^n-1 是素的,则 A 中非零元素都可求逆。那么对应于 32 位和 64 位计算机的,将会是 31 位计算机和 61 位计算机,看上去很不错嘛,期待中。
这样就结束了我们对反码的讨论。
三、原码
这个没有什么可说的,2^n 个状态直接表示 0~2^n-1,如果要引入负数,则让最高位为 1 的表示负数, 所以 0x 代表 x,而 1x 代表 -x.
原码最好的地方是它简单,最不好的地方在于它没有良好的代数结构。
计算机原理原码补码转换求教
来源:http://topic.csdn.net/u/20081017/12/fa19a398-0599-4c10-a2f8-5e101058ca1d.html?1574397430
n = 0x80000000 ;
printf("%d\n",n);
这个结果为什么是-2147483648呀??
原码应该是10000000 00000000 00000000 00000000
反码 11111111 11111111 11111111 11111111
补码是什么呀?要是 10000000 00000000 00000000 00000000的话结果为什么会是-2147483648????
补码就是说,最高位是符号位,1表示负数;
其余位取反加1是所要表示绝对值的原码,即10000000 00000000 00000000=2147483648,
所以这个说是-2147483648
我想问下0x80000000为什么print出来是-2147483648呀?原码的话是10000000 00000000 00000000 00000000 那不就是-0么??应该print结果是0吧??
上面的问题我查到了 最后想问 10000000的补码是什么? 如果取反加1的话会溢出么?取反是11111111在加1后,怎么加?符号位变么?还是变成9位的1 00000000 ? 那么补码是什么??
n是signed类型,n是10000000 00000000 00000000 00000000,超过了正数的范围,计算机将它视为补码
得到的是最小的负数(int型)-2147483648
-0没有补码型式,10000000 00000000 00000000 00000000本身就被视作补码
好好看书吧!~书上有……
在目前的计算机体系中,一般都用“补码”来存储整数——C语言的printf函数族便对%d或%i格式的按补码解释。注意,只是把对应内存中的4个字节按补码解释成整型数,不管它原来或是应该是什么。
如果调用printf函数时,像lz这样:
printf("%d\n",n);
编译器会将n的值——0x80000000压栈,接下来是一个静态字符串的地址压栈,然后调用printf函数;
printf函数获得第一个参数——字符串地址,于是解析这个字符串,发现其中有'%'后面跟着'd',于是它知道,这应该是说还有个4字节的整型传过来;
于是printf函数从栈上pop出一个双字,——这个值是0x80000000;
采用%d格式将这个数变成字符串:
(按照我们的想法,就是负号后“按位取反,末位加一”,0x80000000变成0x7FFFFFFF,再加一变成0x80000000,直接变成十进制就是2147483648,当然前面的负号别忘了。) 计算机里面就没有原码的存储,所谓的原码是正数的补码和原码二进制上的相等
计算机里是补码表示的
0x80000000
补码
10000000 00000000 00000000 00000000
表示32最小的负数
-2147483648
计算机里就是以补码形式存储数据的
关于-128的补码说明
来源:http://hi.baidu.com/maml897/blog/item/f0992ce8709db235b90e2d55.html
2009-11-05 17:09
8位定点整数的原码表示范围是-127到127
8位定点整数的补码表示范围是-128到127
对上两句说明:
用原码表示:
01111111 8位有符号最大的数 是127
11111111 8位有符号最小的数 是-127
用补码表示:
01111111 8位有符号最大的数 是127
10000001 8位有符号最小的数 是-127
但是还有一个更小的就是10000000 把这个规定为 -128的补码
在8位长度下,-128的原码与反码都不存在,因为一个字节的有符号数的原码范围是: -127 ~ + 127 ,既然不存在 -128的原码那么就无法求出 -128 的补码了,怎么办?
前几天听汇编老师曲俊华讲过,-128这样的数很特殊,不是取反加一求补码。
-2^n是个特殊数(n为x数值位的长度):它补码的求法应按照公式进行运算:
如:
-128
“[-2^7]补”=(2^8)+(-2^7)=10000000
原来还有更牛x的解释:http://blog.csdn.net/band_of_brothers/archive/2008/07/04/2612460.aspx
一般的说法是负数的补码为其原码除符号位外取反然后总体加一,也就是说,要得到一个负数数的补码,要先知道这个负数的原码才行。那么,问题出现了,在8位长度下,-128的原码与反码都不存在,因为一个字节的有符号数的原码范围是: -127 ~ + 127 ,既然不存在 -128的原码那么就无法求出 -128 的补码了,怎么办?
其实,这个问题的实际意义是,既然说计算机内部的有符号整数都是补码,那么怎么才能有效的实现这一设计呢?潜台词是:根据上面由原码推导出补码的理论,如果是正数,计算机得到其原码,也就是得到了其补码(正数补码等同于原码),如果是负数,先得到其原码,然后再取反加一就可以了。也就是说按这个思维设计编译器,或计算机电路,就可以了。但是如果出现开始说的 -128的补码问题,这个设计就不能工作了。其实,在真正的设计中,这种获得负数补码的“取反加一”的方案根本没有实施过!
拿现代的计算机举例,输入设备只有键盘,通过编辑器程序把键盘的扫描码变成ascii码存储起来,比如输入 ‘a’就存储 0x61 ,输入 ‘5’就存储 0x35 ,这些都是ascii码。当你想得到真正的机器数时(注意,机器数和ascii是不同的,字符‘5’的ascii码是0x35,而数字5的机器数是 0x5),需要借助编译器把表示数字的字符串从ascii码变成真正的机器数,比如你想得到 56的机器数(就是0x38),就可以输入语句“ db 56 ”,让汇编器程序帮你把56的ascii码字符串 :“0x35, 0x36”,转变成真正的机器数 0x38。这一转化需要这样一段程序:先把 ‘5’与 ‘0’做减法,就是 0x35 – 0x30 得到 0x5 (这一步就将ascii字符5变成了真正的机器数5),再把 0x5 与 0xa (就是十进制 10)相乘得到 0x32 (就是十进制 50) ,然后再把 ‘6’与 ‘0’做减法,就是 0x36 – 0x30 得到 0x6 (就是机器数6),最后把 0x32 与 0x6 相加,得到 0x38 ,就是机器数56了。
如果想得到 -56 ,就用语句 “ db -56 ”,这里得到机器数 56 的步骤与上面一致,只是最后要把 0xff 与 0x38 相乘,就是 -1 * 56 ,最后得到 0xc8 ,就是 -56 的补码。
这里我们看到,得到 -56 得补码时根本没用什么“取反加一”,这里处理的过程都是很自然的,只要考虑各个数值的运算,而不用考虑数值的补码形式,以及如何得到负数的补码。为什么?因为加法,有符号乘法等指令的电路,都是按补码输入,补码输出来设计的,你只要保证输入的是补码,输出的肯定也是补码。所以,只要你输入 -1 的补码 0xff ,与56的补码 0x38 ,得到的自然是 -56 的补码 0x c8 。综上,我们在获得 -56 的补码时,没有采取先得到 -56 的原码,然后除符号位外各位取反,最后再总体加一的方式。
由此可见,计算机是一个相当“封闭”的系统,他内部所有的有符号整数都是补码形式存在的,只要按数值的实际意义考虑问题,不用担心它的存储方式,比如想让 -56 与 6 相乘,你根本不用担心结果那个负数怎么变成补码,所有的运算电路都是按补码设计的。换句话说,“封闭”的计算机内部的有符号数都是补码的形式存在的,你根本不用考虑什么原码,什么取反加一了,你只要考虑你想要的数值就可以了,不要担心他怎么存储的。
有了上面那个ascii码到机器数的转换程序后,数字的输入就不再是问题了,但是,考虑的更远一点,如果在计算机的“蛮荒时代”,所有的指令,数据,都要用机器语言一位一位的输入时(搬开关、打孔),那时的有符号整数又怎么输入呢?确实,在那个环境下,就只能用我们的大脑计算了,把数字在大脑中转换成补码的样子,然后输入。其实,真正需要我们在大脑里转换然后再输入的数只有5个,分别是 ‘+’,‘-’,‘0’,0,-1 ,他们的补码分别是:0x2b ,0x2d,0x30,0x00,0xff ,好了,用这5个补码和机器指令编出我们刚才讲的ascii码转化机器数的程序,从此以后,我们只要输入数字的ascii字符串就可以了,让这个程序帮我们转换成补码,不用再辛苦的计算补码了。
深入到机器层,机器层面也根本用不着“原码取反加一”,因为机器里的所有数字,都是人工或者是编译器输入的,已经转换成了补码了,他本身已经是一个完备而封闭的系统了,根本没有接口接收其他有符整数的编码方案了。需要注意的是有一个取补指令neg ,这里的取补不是本来意义的“取反加一”(本来意义的“取反加一”只对负数),而是不论正负,把每一位取反,最后加一,就是说 neg 20 结果为 -20 ,neg -56 结果为56 ,就相当于把操作数与 -1 像乘了。这显然与为得到负数的补码采取的“取反加一”截然不同。当两个数做减法时,比如:有符号整数 60 (0x3c) 与 有符号整数 77 (0x4d)相减,加法器有一段电路把减数77取反加一,但是,请注意,这个电路跟neg一样,不管正负每位取反最后加一,就相当于用 乘法指令imul 把 操作数 与 -1 相乘了。这也跟求负数的补码采取的“取反加一”截然不同。
综上,无论在编译器层面还是机器层面,“负数的补码为其原码除符号位外取反然后总体加一”这个方法都没有用上,这只是教科书上提供的便于记忆的方法而已。
根据上面的说法,分析下c中具体的问题: c中int占4个字节,表示范围从 – 2147483648 到 2147483647 。
问题1:int -2147483648 ;编译后的值正确吗?
上面说的转换程序,这个编译的步骤为: 计算2*10^9 + 2*10^8+ …… +4*10+8 ,因为int只能表示到 2147483647 (0x7fff ffff),再加1的话就溢出了,得到了:0x8000 0000 ,(就是 -2147483648) ,然后再把这个结果与 -1 就是 0xffff ffff 相乘,得到的结果也溢出了,为:0x8000 0000 ,但是这恰好是 – 2147483648的补码,所以,虽然编译中虽然出现了两次溢出,但得到的结果是正确的。
问题2:int x = - 2147483648; 那么 –x 是 2147483648吗?
因为 – 2147483648 (0x8000 0000)与-1 (0xfff ffff)相乘结果为:0x8000 0000 就是– 2147483648 ,所以 –x 的结果还是 – 2147483648 而不是2147483648 。 所以,凡是结果可能超越表示范围的时候,一定要慎重,因为结果可能是你意料之外的。
其他资料
-1和-128由于没有原码,所以就不能用符号位不变,其他位取反的方法求他们的补码了,而只能用补码的公式去求解.
即:二进制位串X为8位,对于大于等于-128小于0的,其补码公式为:
[X]补=2^8-|X|
若X=-1
按二进制算法求-1的补码即:100000000-00000001=11111111
按十进制算法求-1的补码即:256-1=255,然后将结果255转换成二进制11111111即可
同理:-128的补码求法:
按二进制算法求-128的补码即:100000000-10000000=10000000
按十进制算法求-128的补码即:256-128=128,然后将结果128转换成二进制
10000000即可.
-1有原码,怎么说没有呢
闲扯原码、反码、补码
来源:http://xiaohe9527.javaeye.com/blog/463839
相信大家看到这个标题都不屑一顾,因为在任何一本计算机基础知识书的第一章都有他们的解释,但是在书上我们只能找到一些简单的定义,没次看过之后不久就忘了。最近论坛里有人问起这些概念,看到很多人的回复是以前看过现在忘了去看看某某书之类,很少有给出一个合理的解释。于是本人就开始思考(虽然上帝会发笑,我还是要思考。),于是得出了以下的结论。
数值在计算机中表示形式为机器数,计算机只能识别0和1,使用的是二进制,而在日常生活中人们使用的是十进制,"正如亚里士多德早就指出的那样,今天十进制的广泛采用,只不过我们绝大多数人生来具有10个手指头这个解剖学事实的结果.尽管在历史上手指计数(5,10进制)的实践要比二或三进制计数出现的晚."(摘自<<数学发展史>>有空大家可以看看哦~,很有意思的).为了能方便的与二进制转换,就使用了十六进制(2 4)和八进制(23).下面进入正题.
数值有正负之分,计算机就用一个数的最高位存放符号(0为正,1为负).这就是机器数的原码了.假设机器能处理的位数为8.即字长为1byte,原码能表示数值的范围为
(-127~-0 +0~127)共256个.
有了数值的表示方法就可以对数进行算术运算.但是很快就发现用带符号位的原码进行乘除运算时结果正确,而在加减运算的时候就出现了问题,如下: 假设字长为8bits
( 1 ) 10- ( 1 )10 = ( 1 )10 + ( -1 )10 = ( 0 )10
(00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 显然不正确.
因为在两个整数的加法运算中是没有问题的,于是就发现问题出现在带符号位的负数身上,对除符号位外的其余各位逐位取反就产生了反码.反码的取值空间和原码相同且一一对应. 下面是反码的减法运算:
( 1 )10 - ( 1 ) 10= ( 1 ) 10+ ( -1 ) 10= ( 0 )10
(00000001) 反+ (11111110)反 = (11111111)反 = ( -0 ) 有问题.
( 1 )10 - ( 2)10 = ( 1 )10 + ( -2 )10 = ( -1 )10
(00000001) 反+ (11111101)反 = (11111110)反 = ( -1 ) 正确
问题出现在(+0)和(-0)上,在人们的计算概念中零是没有正负之分的.(印度人首先将零作为标记并放入运算之中,包含有零号的印度数学和十进制计数对人类文明的贡献极大).
于是就引入了补码概念. 负数的补码就是对反码加一,而正数不变,正数的原码反码补码是一样的.在补码中用(-128)代替了(-0),所以补码的表示范围为:
(-128~0~127)共256个.
注意:(-128)没有相对应的原码和反码, (-128) = (10000000) 补码的加减运算如下:
( 1 ) 10- ( 1 ) 10= ( 1 )10 + ( -1 )10 = ( 0 )10
(00000001)补 + (11111111)补 = (00000000)补 = ( 0 ) 正确
( 1 ) 10- ( 2) 10= ( 1 )10 + ( -2 )10 = ( -1 )10
(00000001) 补+ (11111110) 补= (11111111)补 = ( -1 ) 正确
所以补码的设计目的是:
⑴使符号位能与有效值部分一起参加运算,从而简化运算规则.
⑵使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计
所有这些转换都是在计算机的最底层进行的,而在我们使用的汇编、C等其他高级语言中使用的都是原码。看了上面这些大家应该对原码、反码、补码有了新的认识了吧!
原码、反码和补码
来源:http://blog.csdn.net/sonic_yu/archive/2008/02/20/2108808.aspx
一、原码
求原码:X≥0,则符号位为0,其余照抄;
X≤0,则符号位为1,其余照抄。
【例1】X=+1001001 [X]原 = 01001001
【例2】X=-1001001 [X]原 = 11001001
二、反码
求反码:若X≥0,符号位为0,其余照抄;
若X≤0,符号位为1,其余按位取反。
【例3】X=+1001001 [X]反 = 01001001
【例4】X=-1001001 [X]反 = 10110110
三、补码
求补码:若X≥0,符号位为0,其余照抄;
若X≤0,符号位为1,其余取反后,最低位加1。
【例5】X=+1001001 [X]补 = 01001001
【例6】X=-1001001 [X]补 = 10110111
四、补码加减法
计算机中实际上只有加法,减法运算转换成加法运算进行,乘法运算转换成加法运算进行,除法运算转换成减法运算进行。用补码可以很方便的进行这种运算。
1、补码加法
[X+Y]补 = [X]补 + [Y]补
【例7】X=+0110011,Y=-0101001,求[X+Y]补
[X]补=00110011 [Y]补=11010111
[X+Y]补 = [X]补 + [Y]补 = 00110011+11010111=00001010
注:因为计算机中运算器的位长是固定的,上述运算中产生的最高位进位将丢掉,所以结果不是
100001010,而是00001010。
2、补码减法
[X-Y]补 = [X]补 - [Y]补 = [X]补 + [-Y]补
其中[-Y]补称为负补,求负补的方法是:对补码的每一位(包括符号位)求反,最后末位加“1”。
【例8】X=+0111001,Y=+1001101,求[X-Y]补
[X]补=00111001 [Y]补=01001101 [-Y]补 = 10110011
[X-Y]补 = [X]补 + [-Y]补 = 00111001+10110011=11101100
五、数的表示范围
通过上面的学习,我们就可以知道计算机如果用一个字节表示一个整数的时候,如果是无符号数,可以表示0~255共256个数(00000000~11111111),如果是有符号数则能表示-128~127共256个数(10000000~01111111)。如果两个字节表示一个整数,则共有65536个数可以表示,大部分程序设计语言中整数的范围都是-32768~32767的原因,可以看出这种整数类型是16位的有符号数,而且是补码表示的。
正数的反码和补码都是和原码相同。
为什么要设立补码呢?
第一是为了能让计算机执行减法:
[a-b]补=a补+(-b)补
第二个原因是为了统一正0和负0
正零:00000000
负零:10000000
这两个数其实都是0,但他们的原码却有不同的表示。
但是他们的补码是一样的,都是00000000
特别注意,如果+1之后有进位的,要一直往前进位,包括符号位!(这和反码是不同的!)
[10000000]补
=[10000000]反+1
=11111111+1
=(1)00000000
=00000000(最高位溢出了,符号位变成了0)
有人会问
10000000这个补码表示的哪个数的补码呢?
其实这是一个规定,这个数表示的是-128
所以n位补码能表示的范围是
-2^(n-1)到2^(n-1)-1
比n位原码能表示的数多一个
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。