MyException - 我的异常网
当前位置:我的异常网» 综合 » 由-128的补码引入的深层次思考

由-128的补码引入的深层次思考

www.MyException.Cn  网友分享于:2013-02-20  浏览:2次
由-128的补码引出的深层次思考

 一般的说法是负数的补码为其原码除符号位外取反然后总体加一,也就是说,要得到一个负数数的补码,要先知道这个负数的原码才行。那么,问题出现了,在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楼JustJavaC2013-02-04 09:51
以前也写过一篇类似的文章:http://justjavac.iteye.com/blog/1698691

文章评论

软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有