从java toBinaryString() 看计算机数值存储方式(原码、反码、补码)
一、tobinarystring 方法及其含义
1.1 方法说明
该方法位于java.lang.integer
类中
方法签名:public static string tobinarystring(int i)
含义:返回参数数值的补码形式,正数则忽略前面的0。(官方注释:返回表示传入参数的一个无符号(这里无符号大概单纯指数值前面没有+-号,实则是有符号位) 的二进制字符串。如果参数为负数x,返回的值则为 2^32 + x 【就是它的补码】)
1.2 使用示例
system.out.println(integer.tobinarystring(3)); // 输出:11 system.out.println(integer.tobinarystring(-248)); // 输出:11111111111111111111111100001000
二、计算机数值存储原理
2.1 相关概念
计算机中,包括如java在内的各语言均是用补码来存储数值的
- 原码:最高位表示符号位 "1"为负、"0"为正,其他位为正常二进制形式。
- 反码:正数的反码即原码本身;负数则在原码的基础上,除符号位之外的其他各位置反。
- 补码:正数的补码即原码本身;负数则在反码基础上+1。
例:
正数:3 = 0000 0011[原 | 反 | 补]
负数:-3 = 1000 0011[原] = 1111 1100[反] = 1111 1101[补]
注:
- 以上规则只是方便我们人来进行推算,而在实际情况中存在不适用的情况,如:1000 0000[补] = -128。关于这点,后面会解释到。
2.2 为什么会引入补码
通过引入补码,cpu只需单纯的加法器即可完成加减运算,节约成本。
2.3 背景知识:模系统中的运算及理解
1) 一个数加上模,等于它自身
在任意模系统中,其模为:数所能表示的最大值+1。具体到对于n位二进制,其模则为2^n。
注:但对于一般的加减运算来说,是不需要回到自身的(显然的事实,但之前莫名其妙老想着回自身),直接加一个数就行了,只是当加数>=模时,会超过模(溢出高位舍去)然后到达一个值。
示例及理解证明:
- 钟表上(把12换作0),所能表示的最大数为11,因此其模就为11+1=12。
根据常识,显然从4点转动12小时,又会回到4点本身,即 4 + 12 = 4 - 对于2 bit系统,其模为 11b + 1 = 2^2 = 4。
例:01b + (1)00b = 01b【此处(1)00b表示4,因为高位溢出舍去,显然还是等于它自身】
2) 什么是同余数?
对于同一个除数的余数相等,则这些数称为同余数。具体的对于数a、b、c,若有a%c == b%c,则称a、b彼此是关于c的同余数。
3) 在模系统中,同余数有什么特性?
在模系统中,加上数a可等价为加上它的同余数b。
示例及原因:
在模 n = 12 的系统中,假设有数 a = -4,构造它的同余数 "k*n + a", k=0,1,2...【公式含义:任意倍数的模n加上a,因此显然是a关于n的同余数(因为倍数k都作为除法的结果上去了,余数始终为 a )】比如 b = 1*12 + (-4) = 8
根据同余数的构造原理显然可知,在模系统中,加上数a等价于加上同余数b。
【证明步骤:】
c + b = c + k*n + a
因为上述背景知识1),所以 c + k*n = c
所以 c + b = c + a
2.4 将减法转换为加法
- 思路1:基于同余数的特性,在模n的系统中,对于减法 a - b,可进行以下转化:a - b => a + (-b),令 c = n + (-b)【由同余数构造可知,显然c是(-b)的最小正同余数】,因此 a + (-b) => a + c,而 (-b)的补码正好就等于c,也就是最小正同余数,所以 a - b => a + (-b)补。
- 思路2:基于模原理,减法则能换成加法来做:令 c = (n-a) + (a-b) = n-b【 n-a 为了使a+c超过模重置为0,(a-b) 移到具体位置】,而 n-b 显然是(-b)的同余数【根据同余数构造可知】,因此 a + (-b) = a + c,而 (-b) 的补码正好等于c。因此 a + b = a + (-b)补。
因此,cpu只需加法器也能完成减法运算。
注:至于为什么(-b)的补码正好就是最小正同余数c,暂未深挖。就是刚好是。
2.5 补码系统中,符号位也要参与运算
使用补码的系统中首位(即符号位)也要参与正常运算,只是当溢出时可能会发生符号转换,未溢出时运算均是正确的。
(正确性证明,可参考其他博文)
比如在8bit系统中:127 + 1 = [0111 1111]补 + [0000 0001]补 = [1000 0000]补 = -128
2.6 为什么定义 1000 0000[补] = -128
网上很多只是说为了不浪费而简单定义,但光简单定义肯定不行的,肯定还需要符合运算规律。
其实计算机对补码的存储和解释,不一定非要经过源码这一环,那是对人的一种换算方式,1000 0000[补] = -128 是符合运算规律的。
比如:-128 + 1 = -127
[1000 0000]补 + [0000 0001]补 = [1000 0001]补 = [1111 1111]原 = -127
不只是1000 0000[补]=-128,在 n bit系统中,对于首位为1其他位为0的数,其值为 -2^(n-1)
部分参考: