负数的二进制表示
问题: 假设有一个 int 类型的数,值为 5,那么,我们知道它在计算机中表示为:00000000 00000000 00000000 00000101
5 转换成二制是 101,不过 int 类型的数占用 4 字节(32 位),所以前面填了一堆 0。
-5 在计算机中如何表示?
在计算机中,
正数是直接用原码表示的,如单字节 5,在计算机中就表示为:0000 0101。
负数以其正值的补码形式表示,如单字节 - 5,在计算机中表示为 1111 1011。
什么叫补码呢?这得从原码,反码说起。
原码:
一个正数的原码,是按照绝对值大小转换成的二进制数;
一个负数的原码,是按照绝对值大小转换成的二进制数,然后最高位补 1。
比如 :00000000 00000000 00000000 00000101
是 5 的 原码。10000000 00000000 00000000 00000101
是 -5 的 原码。
反码:
正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。
比如:**
正数00000000 00000000 00000000 00000101
的反码还是00000000 00000000 00000000 00000101
负数10000000 00000000 00000000 00000101
每一位取反(除符号位),得11111111 11111111 11111111 11111010
。
称:11111111 11111111 11111111 11111010
是 10000000 00000000 00000000 00000101
的反码。
反码是相互的,所以也可称:10000000 00000000 00000000 00000101
和 11111111 11111111 11111111 11111010
互为反码。
补码:
正数的补码与原码相同;
负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加 1.
比如:10000000 00000000 00000000 00000101
的反码是:11111111 11111111 11111111 11111010
。
那么,补码为:11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011
。转换为十六进制:0xFFFFFFFB。
我们来看整数 - 1 在计算机中如何表示。
假设这也是一个 int 类型,那么:
1、先取 - 1 的原码:10000000 00000000 00000000 00000001
2、得反码: 11111111 11111111 11111111 11111110
(除符号位按位取反)
3、得补码:11111111 11111111 11111111 11111111
可见,-1 在计算机里用二进制表达就是全 1。16 进制为:0xFFFFFF
问题:
x 为 int 类型,为什么 -x=!x+1 ?
原因:
由于 x 为一任意 int 型正整数,左边 - x 表示取 x 的相反数后的二进制形式,右式 !x+1 表示先将 x 的二进制按位取反后再加一得到的二进制
形式。所以左右两个二进制相同 “
在计算机中,负数以原码的补码形式表达。
总结
- 正数的反码和补码都与原码相同。
- 而负数的反码为对该数的原码除符号位外各位取反。
- 负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加 1
:::info
负数在计算机中表示:负数的原码(负数的绝对值 → 转成二进制 → 最高位补 1)→ 反码(除符号位外各位取反)→ 补码(末位+1)
==> 一个正数对应的负数 = 正数二进制按位取反后加一
:::
概念
- 原码表示法规定:用符号位和数值表示带符号数,正数的符号位用 “0” 表示,负数的符号位用 “1” 表示,数值部分用二进制形式表示。
- 反码表示法规定:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。
- 补码表示法规定:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加 1.
- 正零和负零的补码相同,[+0] 补 =[-0] 补 = 0000 0000B
移位操作
1.<<,有符号左移位,将运算数的二进制整体左移指定位数,低位用 0 补齐。
int leftShift = 10;
System.out.println("十进制:" + leftShift + ", 二进制:" + Integer.toBinaryString(leftShift));
int newLeftShift = letfShift << 2;
System.out.println("左移2位后十进制:" + newLeftShift + ", 左移2位后二进制" + Integer.toBinaryString(newLeftShift)); //正整数x左移n位后的十进制结果,x = x * 2^n
以上是正整数,运算结果如下。
接下来看看将负数进行左移 2 位操作是什么情况,运算结果如下。
为什么会 - 10 的二进制会出现这么多的 1 呢?仔细数一下刚好有 32 位。首先需要了解的是 Java 负数存储是以补码形式存储的(补码 = 反码 + 1),10 的二进制是 1010,它的反码就是 0101,再加 1 就是补码 0110。那为什么会多出来那么多 1 呢?这是因为 int 型在 Java 中占 8 个字节,刚好 32 位,10 原码的高位全是 0,它的反码自然高位就变成了 1。所以整体左移 2 位,低位以 0 补齐,最后的运算结果就是 x = (|x| + 2^n)。
2. >>,有符号右移位,将运算数的二进制整体右移指定位数,整数高位用 0 补齐,负数高位用 1 补齐(保持负数符号不变)。
int rightShift = 10;
System.out.println("十进制:" + rightShift + ", 二进制:" + Integer.toBinaryString(rightShift));
int newRightShift = rightShift >> 2;
System.out.println("右移2位后十进制:" + newRightShift + ", 右移2位后二进制" + Integer.toBinaryString(newRightShift)); //右移n位后的运算数x十进制结果,x = x / 2
以上是正整数,运算结果如下。
接下来看看将负数进行右移 2 位操作是什么情况,运算结果如下。
负数的有符号右移基本原理还是和左移相同,不同的是结果的计算,因为这是有符号的右移,一直右移最后的结果就会是 - 1。归纳起来就是,如果运算数是偶数,那么它的运算结果就是 x = -(|x| / 2),如果运算数是奇数,那么它的运算结果就是 x = -(|x| / 2) - 1。
3. >>>,无符号右移位,不管正数还是负数,高位都用 0 补齐(忽略符号位)
先看正数,正数的 >>> 无符号右移位和 >> 有符号右移位计算结果相同
int rightShift = 10;
System.out.println("十进制:" + rightShift + ", 二进制:" + Integer.toBinaryString(rightShift));
int newRightShift = rightShift >>> 2;
System.out.println("右移2位后十进制:" + newRightShift + ", 右移2位后二进制" + Integer.toBinaryString(newRightShift)); //右移n位后的云算数x十进制结果,x = x / 2
以上是正整数,运算结果如下。
接下来看负整数,运算结果如下。
虽然无符号移位后的二进制和有符号移位后的二进制看起来相同的,但结果大相径庭,记住有符号右移位操作,实际上是忽略符号的算术操作,即高位统一补 0。
- 或:| 相同为 1,不相同为 0
- 位运算、按位与运算、与运算:& 都是 1 才是 1,否则是 0
- 异或:^ 不相同为 1,相同为 0
- 右移:>> 除以 2
- 左移:<< 乘以 2
- 无符号位右移:>>> 忽略符号位,空位以 0 补齐
- 没有 <<< 这种运算符,因为左移都是补零,没有正负数的区别。