机器数有三种表示形式:原码、反码、补码。
转换规则
正数
符号位以0作为标识,三种形式完全一致。
负数
符号位以1作为标识,三种形式存在一定的转换规则。
简单来说,原码对数值位(符号位不参与)取反得反码,反码+1(符号位参与)得补码。
计算机内部运算模式
在计算机内部,数值参与运算时都是以补码的形式进行的。
会以 原码 >> 补码 ---运算处理--- 补码 >> 原码 的方式进行处理。
所以,如果我们手工计算时,也需要遵循此流程。从下面的例子里使用的windows自带的计算器也可以看出,其运算二进制时都是将我输入的二进制以补码的格式进行处理和运算的。
实例
这里我们使用对bit位数据最为敏感的位运算符做示例。
就拿按位取反运算符来说,这里有个小公式:
~ num = -(num + 1)
即:~ -2 = 1 ~ -1 = 0 ~ 0 = -1 ~ 1 = -2 ~ 2 = -3
公式只是方便我们快速运算,使用的同时应该理解它的内部原理,我们手动运算一下:
1:~ 1 = -2
原码:0000 0001 表示层
补码:0000 0001 计算机内部,会参与运算
取反:1111 1110 此时得到的依然是某数值 x 的补码 需要将其转为原码
数值 x 的符号位为 1 ,则 x 为负数:补码 1111 1110 减去 1 得到 x 的反码为 1111 1101 对数值位取反得 x 的原码为 1000 0010 转为 10 进制得 x 的值为 -2
0: ~ 0 = -1
原码:0000 0000
补码:0000 0000
取反:1111 1111
数值 x 的符号位为 1 ,则 x 为负数:补码 1111 1111 减去 1 得到 x 的反码为 1111 1110 对数值位取反得 x 的原码为 1000 0001 转为 10 进制得 x 的值为 -1
-1: ~ -1 = 0
原码:1000 0001 对数值位取反得反码 1111 1110 加 1 的补码为 1111 1111
补码:1111 1111
取反:0000 0000 此时依然是某数值 x 的补码 我们需要将其转为原码
数值 x 的符号位为 0 ,则 x 为正数:补码 = 反码 = 原码为 0000 0000 转为 10 进制得 x 的值为 0
一定要注意:首先你需要把数值转换为其补码的形式,才能参与位运算,同时你得到的结果也是某数值的补码,需要转换为其原码,再转换成十进制得到我们想要的结果。
补码运算的优点
八位的二进制能表示 256 个数值,最高位作为符号位,剩余七位作为数值位。但此时原码和反码都会产生正负0的问题。这样的话能表示的范围其实是 -127 ~ -0 / +0 ~ 127,然而 +-0 并没有什么意义。但我们如果使用补码的话:
+0 | -0 | |
原码 | 0000 0000 | 1000 0000 |
反码 | 0000 0000 | 1111 1111 |
补码 | 0000 0000 | 0000 0000 |
可以看到,补码将正负零的问题解决,+-0 用补码表示都是 0000 0000,而 1000 0000 可以用来表示另一个数值(这个数值就是规定好的了,比如 8 位下会是 -128),这样表示的数值范围就是 -128 ~ 127。
按规则即可转换
0000 0000(补码) ~ 0111 1111(补码) = 0 ~ 127
1000 0001(补码) ~ 1111 1111(补码) = -127 ~ -1
不存在转换规则一说
1000 0000 = -128
1000 0000 就是 -128,你不要把它当做补码原码还是反码,它就是它,计算机见了它就会立刻得出它是表示的 -128,这就是内部规定。我们约定好的将 1000 0000 这个机器数作为 -128 的标示。
因为看到很多回答者还一本正经的把它当补码去-1计算反码,但符号位没参与运算。还有的把她当原码去求反码,把符号位也求反了,把我看得一愣一愣的。
1000 0000 这货在原码和反码的场景下是用来表示 -0 的,这让计算机很困扰,于是计算机采用了反码场景,把有歧义的 1000 0000 单独拿出去标示 -128 这个数。1000 0000 并不会参与什么补码转原码的计算,计算机看到它就直接得出 -128。
这是以 8位 数据长度说的,16位, 32位都一样。
比如 16 位,能表示 2 ^ 15 个负数,即
1111 1111 1111 1111(原码) ~ 1000 0000 0000 0000(原码) 这里的 1000 0000 0000 0000 即为 -0,则我们可以让它去表示比 1111 1111 1111 1111 还小 1 的数值,因为 1111 1111 1111 1111 的数值为 - (2 ^ 15 - 1),所以要标示的即为 - (2 ^ 15) = -32768 。
1111 1111 1111 1111 的补码为:1000 0000 0000 0000 + 1 = 1000 0000 0000 0001
那么 1000 0000 0000 0000 要单独被拿出来去标示的为比它还要小1的数值
按位取反运算符的运用技巧
~ -1 = 0 ~ 0 = -1 ~ 1 = -2 ~ 2 = -3 ...
在高级语言中,非0即为真,只有0为假。但在一些定位方法中,-1 表示查找失败,0 表示以子字符串开头,例如 js 中的 indexOf 方法:
// indexOf 方法的返回值有 -1 和 n(n >= 0)
// 当返回 -1 时说明查找失败,返回 n 时说明查找 成功
//但失败会是 -1,成功还可以是 0,这就让条件判断会有一些啰嗦了
if ("hello world".indexOf("hello") >= 0) {
console.log("查找成功");
} else {
console.log("查找失败");
}
//如果使用位运算符呢 查找成功时的返回值为 n(n <= -1)
if (~ "hello world".indexOf("hello")) {
console.log("查找成功");
} else {
console.log("查找失败");
}
这样就能让代码更为简洁,虽然有些晦涩....