怎么就想到这里了
看了一道面试题目,如下所示:
byte b = (byte)129;
System.out.println(b);
问输出结果。
这个题目考察了四个知识点:
- 整数的字面量默认是 int 类型的。而 int 类型占 32 个 bit 。
- byte 类型的是 8 ge bit 。能够表示的最大正整数是 127.
- 补码的计算公式。
- 强制类型转换
129 = 2 的 7 次方 + 1
所以在内存中的表示为: 0000 0000 0000 0000 0000 0000 1000 0001
byte 强转后 : 1000 0001
前面的 16 位都已经消失了。 我们看到最高位是一个 1 ,说明这是一个负数。
负数,我们知道计算机里面,负数是用补码表示的,所以这东西可不是表示的 -1
它表示的是某个负数的补码。例如我们现在有个负数了 A ,那 A的补码 = ~A +1.
现在我们有补码了所以需要 ~(A的补码 -1) = A ,做这样的一个操作。计算的过程如下所示:
我们先将符号去掉
补码 -1: 000 0001 - 000 0001 = 000 0000
取反: 111 1111
111 1111 换算成 10 进制:2 的 7 方 - 1 = 127
所以答案是 -127
本人一直搞不清楚补码这个东西是怎么来,就像知道π这么个常量,我们却不知道它是怎么的出来的。
于是乎一顿查资料。整理了大概的思路。给大家的整理一下。
补码的原理
在学习计算组成的时候,就死记硬背,补码是为了将加减乘除都转成加法而设计的。
这样计算机只要解决加法的逻辑,其他的运算只要转变一下就可以使用加法了。
例如,减法可以转成加上一个负数,乘法可以转成 n 个相同的数相加,除法可以降为减法。
在这个基础上,就可以实现更加复杂的运算了。
所以重点就在于如何实现的负数的加法。要想知道补码的原理,还是要从计算的存储说起。
我们知道计算存储数据是一个固定位数,就拿 Java 里面的 byte 来说吧,它是 8 位的。
我们想一个问题,从 0 开始,我一直加 1 ,当加到第 2 的 8 次方的时候,是不是又
回到 0 了。
如下面的代码:
byte bb = 0 ;
for (int i = 0; i < Math.pow(2, 8); i++) {
bb++;
System.out.println(String.format("第 %s 次加1 , bb 的值为: %s" , i+1 , bb));
}
从这里面我们可以知道,存储位数固定回导致数据循环出现。这也是计算机和数学的区别。
在数学上,数字就只是一个数字,它不占用任何的内存空间。它是存在于人们大脑的抽象的概念。
但是到了计算里面,一个数字真真实实保存在一个磁盘的 bit 。
接着来说,我写上面这个循环的意义。不知道您看每看过刘慈欣的小说《诗云》,里面神级文明为了写出想李白一样
意境的诗文,准备使用排列组合的办法枚举出所有文字的组合,然后再从里面挑出意境高远的诗句。他做这件事情之前
给人类解释说,如果想到河的对岸有两种办法,一个是涉水过河,另外一个办法从对岸的反方向走,终有一天你会走到
河的对岸,因为地球是球型的。讲这个和我们说的补码有什么关系呢?请看下图,
A - B 的们可以看成是从 A 点逆时针往走 B 的长度。但是计算里面存储结构式有周期性,好比上图的圆形,我们可以从
反方向顺时针走到 B 点。
我们知道补码=负数取反 + 1 . 取反式对数字部分取泛,不包括符号位。我们知道一个二进制的数字,最高位上的 1 代表了
数字位 max,剩余的位上所有的 1 ,加起来等于 max-1。例如:
我们以 4 bit 为例子。
1111 代表 10 进制的 15。最高位上的那个 1 对应的 10 进制是 8(1000),其余的 1 对应的 10 进制是 7 (0111)
由上面的图我们可以看出来,从反方向,A 到 B 点走的路程是 ,AC + CB,其中 CB = (2max-1)/2。AC= (2max-1)/2 - AB。
而且 AB 就是我们减去的那个正数的长度。
我们知道负数的反码最高已经是 1 了,所以假设我们从 A 点出发,已经顺时针走过了 AC 的长度,接下来就是 AC 的长度呢?其实就是 max-1 - AB ,也就从剩下所以 1 里面拿掉 AB 所代表那些 1. 举个例。
还是以 4 个 bit 为例子:
-1 的原码是 1001,后三位是数字位,111 - 001 就是 AC 的长度。正好就是 001 取泛的操作的。也就是我说的从剩下所以 1 里面拿掉 AB 所代表那些 1
计算机的加减乘除
计算机是如何进行加减乘除的呢?其实都是转化为两个数字的加法来实现。例如,
A - B 可以转化为 A + (-B),A * B 可以转变为 A 个 B 相加 ,A/B 可以转为 A - B 可以减多少次,减到结果为 0 为至。所以这样看来只要实现两个数据的加法,其他的计算都可以有加法计算出结果。
两个正整数相加的算法:
public static int plusTwoInteger(int a , int b){
int rs = a ^ b;
int mid = a & b ;
int rs_buff = 0 ;
while(mid != 0){
rs = (rs_buff = rs) ^ ( mid = mid << 1);
mid = rs_buff & mid ;
}
return rs ;
}
异或得 1 的位置上说明是 1 和 0组合,相加后就变成了 1,不需要进位,并且的 1 说明,两个数相同位置上都是 1 ,需要进 1 位,我们有 进位操作符号。让再让进 1 之后的数据和做异或的数据做并且操作,看看结果是不是等于 0 ,如等于 0 ,则说明可以停止计算了。所以只需要使用,异或、并且、向左移位、循环着四种级别计算机操作,就能实现加法的计算。实际上计算的加法器就是这样实现的。
感叹
说到这里不尽感叹一些,那些我们习以为常的知识是多么充满智慧。那些研究出计算机的脑洞得有多大才能想出使用补码来解决减法问题的。