计算机中使用二进制表示一个数字,使用科学计数的形式表示浮点数,在Java中使用IEEE 754标准做为二进制浮点数算术标准。IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。有符号数的三种表示方式分别为原码、反码、补码。符号位都是用0表示正1表示负,数值位的表示方法各不相同。在计算中数值统一使用补码表示和存储,使用补码的原因在于可以将符号位与数值位统一处理;同时也可以将加法与减法统一处理。
在讲二进制补码前先介绍下“模”的概念:“模”是指一个计量系统的计数范围,可以将模比喻成一个水桶,水桶有容量大小,这个容量大小就“模”,超过水桶容量的水会溢出。计算机也可以看成一个计量系统,对于数值也会有位数限制,例如uint32的类型值二进制位数是32,计量范围是 0 ∼ 2 32 − 1 0\sim2^{32}-1 0∼232−1,那模就是 2 32 2^{32} 232,超过模的大小就会出现溢出,溢出的位会被自然舍弃。
下面用一个8位的二进制数来看看计算结果,模为 2 8 2^{8} 28
11111111 + 00000001 = 100000000 \begin{array}{cr} &11111111\\ +&00000001\\ \hline =&100000000 \end{array} +=1111111100000001100000000
二进制 11111111 11111111 11111111置换为十进制数为255,上面的列式用十进制表示为255+1=256,256超过了模 2 8 2^8 28,最后的计算结果为是0,而不是256,因为结果中的高位(第9位)被自然舍弃掉了。
任何有模的计量器均可化减法为加法运算,转换的方法为取反后加1。
假设有一个圆盘,将圆盘等分为12个扇区,这样就是一个模计算器,模=12,将每个扇区顺序标号0~11;10号扇区中有一个小球,需要将小球顺序的移动到8号扇区;方法有两种,第一种是顺时针跳10个扇区到8号扇区即10+10=12+8=8,第二种是逆时针跳两个扇区到8号扇区即10-2=8,即10+10=10-2=10+12-2(mod 12);在12为模的系统中加10与减2的效果是一样的,因此只要是减2的运算都可以用加10代替,反过来也是一样加10的运算可以用减2代替,在这样的系统中2和10就互补了。那在以12为模的系统中还有其它这样的补数组合吗?有的,例如:11和1,6和6等,只要两个整数加起来等于模就可以互补。
将上面这个模系统应用到计算机中同样也是可行的,在上面8位二进制的计算系统里模是 2 8 2^8 28,同样减法可以转换成加法,加法可以转换成减法,减法转换成加法时只要把减数转换成相对应的补数就行,把这个补数用在计算机中表示就是补码。
求二进制的补码比较简单,只需要将每一个取反就可以得到补码,例如:
11110011 ⇒ 00001100 01010101 ⇒ 10101010 11110011 \Rightarrow 00001100 \\ 01010101 \Rightarrow 10101010 11110011⇒0000110001010101⇒10101010
但数值为有正负的问题,在计算中会使用1个高位来表示符号,0代表正数,1代表负数。在计算来符号的二进制数补码时符号位不参与进来。
与原码的二进制是一样的,例如+7:
00000111
在8位系统中二进制的表示。
负数的补码是将原码中除符号位的其它所有位取反(0变1 ,1变0)后加1 。
例如:-7
原码:10000111 ->除符号位取反(11111000)->加1(00000001)->补码(11111001)
所有-7的补码是11111001.
数值0的补码表示是唯一的。
[+0]补码=[+0]反码=[+0]原码=00000000
[-0]补码=[11111111]反码+1=00000000
二进制加法运算方法,可以使用进位法 ;先将两个十进制数置换成二进制数,再将二进制数右对齐。
进 位 : 100000010 A : 10001001 B : + 10000101 = 100001110 \begin{array}{cr} 进位:&&100000010\\ A:&&10001001\\ B:&+&10000101\\ \hline &=&100001110 \end{array} 进位:A:B:+=1000000101000100110000101100001110
上下对应位置相加,值为0对应结果位为0,值为1对应结果位为1,值为2对应结果位为0并进行进位。
约定右边为第一位,
第一位:A与B都是1,相加值为2进行一次进位,并在结果位上写上0
第二位:A与B都是0,但进位上是1,相加值为1,不需要进位,结果为1
第三位:A为0B为1,结果和第二位相同
第四位:与第三位相同
第五位:A与B都为0,进位上也是0,相加值为0,结果为0
……
最后第八位上和第一位的计算方法一样,进位可以直接写在结果中的第九位。最后结果为100001110。由于出现了溢出(假设使用的是模为 2 8 2^8 28的计算系统),最终结果将舍弃第九位(00001110)。
二进制加法运算可以使用进位法,相对应的减法也有一个与进位法相对应的运算方法借位法,借位法的运算方法与进位法相反,还是用上面的例子:
借 位 00001100 A : 10110001 B : − 10100101 = 00001100 \begin{array}{cr} 借位&&00001100\\ A:&&10110001\\ B:&-&10100101\\ \hline &=&00001100 \end{array} 借位A:B:−=00001100101100011010010100001100
原理:
二进制减法运算除了使用借位法,还可以将减法转成加法,上面做的模、补码这些知识的铺垫也是为了本篇的主角补全法,补全法的核心思想就是使用补码进行运算。
首先将被减数转换成补码 10100101 → 01011011 10100101 \rightarrow 01011011 10100101→01011011 ,再使用补码做加法运算结果为:
A : 10110001 B : + 10100101 = 101010110 \begin{array}{cr} A:&&10110001\\ B:&+&10100101\\ \hline &=&101010110 \end{array} A:B:+=1011000110100101101010110
模为 2 8 2^8 28的计算系统下需要舍弃一个高位,得到的最后结果为01010110 。
补全法还可以用在十进制的计算模中,假设模=100,计算 56 − 17 = ? 56-17=? 56−17=? ,可以先找到17在模=100中的补数82,再使用$56+82=139$139超过了100,将百位自然舍弃,最后结果为39。
往期推荐:
知识点: Java ReentrantReadWriteLock 读写锁看共享锁与排他锁 理论 原理
知识点: Java公平锁与非公平锁 原理讲解ReentrantLock 锁的饥饿效应及解决办法
知识点: JAVA 悲观锁与乐观锁原理分析 ABA与自旋效率问题分析及解决
Java ThreadLocal 有泄漏内存的风险怎么搞?分析下原理吧