数值的表示
任何存储于计算机中的数据,其本质都是以二进制码存储的。计算机中所有的数都以补码形式存在,加减运算都是补码间的加法运算。为何及其要用补码存储,我们需要了解原码、反码和补码的概念。
原码
原码是符号位加真值的绝对值。有符号的二进制数将最高位作为符号位用来区分正负数。 正数的符号为 0,负数的符号为 1。
对于 8 位二进制数:
1 | +1 的原码为 0000 0001 |
使用原码表示数值会出现两个 0
1 | +0 的原码为 0000 0000 |
因此使用原码表示 8 位二进制数的范围就是 [1111 1111, 0111 1111] 也就是 [-127, 127],里面包括了 +0 和 -0 两个数,总共有 256 个数
反码
- 正数的反码与其原码相同
- 负数的反码是在其原码的基础上,符号位不变,其余逐位取反。
那么对于 8 位二进制数:
1 | +1 = 0000 0001(原码) = 0000 0001(反码) |
这样的话也会有两个 0, 0000 0000(+0) 和 1111 1111(-0)
补码
- 正数的补码是其本身
- 负数的补码是在反码的基础上 +1
对于 8 位二进制数
1 | +1 = 0000 0001(原码) = 0000 0001(反码) = 0000 0001(补码) |
在计算机的运算中,只有加法没有减法,减法就相当于加上这个数的相反数,如果我们将符号位参与运算,并且只保留加法运算,对于原码来说,无法得到正确的结果,比如 1-1=0 这个运算。
- 用原码进行计算减法,结果是不正确的:
1 | 1 - 1 = 1 + (-1) = 0000 0001(原) + 1000 0001(原) = 1000 0010(原) = -2 |
- 用反码进行计算减法,得到结果的真值是正确的,但是对于 0 这个结果具有特殊性。对于人类理解来说 +0 和 -0 是相同的,但对于机器而言,带有符号的 0 没有意义,且 0 会存在 0000 0000 和 1000 0000 两个编码。
1 | 1 - 1 = 1 + (-1) = 0000 0001(反) + 1111 1110(反) = 1111 1111(反) = 1000 0000(原) = -0 |
- 用补码进行计算减法,能够修复原码中 0 的符号正负之分,及存在两个编码的问题。 0 只有一个编码 0000 0000
1 | 1 - 1 = 1 + (-1) = 0000 0001(补) + 1111 1111(补) = 0000 0000(补) = 0000 0000(原) = 0 |
而且可以用 1000 0000(补) 来表示整数 -128
1 | -1 - 127 = -1 + (-127) = 1000 0000(原) + 1111 1111(原) = 1111 1111(补) + 1000 0001(补) = 1000 0000(补) |
实际上就是以前的 -0 用来表示 -128, 因此 -128 并没有原码和反码的表示(对 -128 的补码 1000 0001 算出来的原码是 0000 0000 是不正确的)
在 8 位二进制中:
- 使用原码/反码表示的范围为 [-127, +127], 包含 +0 和 -0 共 256 个数
- 使用补码表示的范围为 [-128, +127], 0 没有符号
由于机器使用补码,能够多表示一个最低数,那么 8 位的二进制数用原码或反码表示的范围是 [-127, 127], 而用补码表示的范围是 [-128, 127]
编程中常用到的 32 为 int 类型,能够表示的范围是 [-2^31, 2^31-1]
INT32
32 位的二进制数可以用 16 进制来表示:也就是将二进制的每相邻 4 位作为一个二进制数,计算出其值,再用 16 进制来表示。
比如 32 位的最大值: 0111 1111 1111 1111 1111 1111 1111 1111 (第一位是符号位)
用 16 进制表示就是: 0x7fff ffff (0x 开头表示 16 进制,16 进制的 15 是 f)
对于有符号的 32 位整数,0x8000 0000 可以表示其最小值
其二进制的原码是: 1000 0000 0000 0000 0000 0000 0000 0000 (最左边的 1 是符号位)
反码是: 1111 1111 1111 1111 1111 1111 1111 1111 (符号位不变,其他取反)
补码是: 1 1000 0000 0000 0000 0000 0000 0000 0000 (反码 +1, 有进位)
补码的值为 -2^31
有符号的 32 位的最大值为 0x7fff ffff
有符号的 32 位的最小值为 0x8000 0000
不用加减乘除做加法
编程题: 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、* 、/四则运算符号
1 | # -*- coding:utf-8 -*- |
在 python 中,要解决这道题比较麻烦的一点在于 python 中的位运算需要进行越界检查,要理解这道题的解决方法需要学习一下关键点:
原码、反码、补码
越界检查
由于 python 的 long 类型可以表示无限位,不存在数值溢出情况,因此要人工设置边界,避免死循环。在编程中常用到的是 32 位整形,因此我们把数值和 0xffffffff 进行按位与操作, 0xffffffff 代表 16 进制下的边界,也就是 32 位整数的边界。正负数判断
正数 & 0xffffffff 得到的数仍是其本身
负数 & 0xffffffff 得到对应二进制数的真值,得到的值会超过最大值
比如 -15 & 0xff = 1000 1111(原) & 1111 1111 = 1111 0001(补) & 1111 1111 = 1111 0001 = 241
而 8 为二进制的最大值为 127,因此 -15 & 0xff 超过了 127,因此我们可以根据结果与最大值比较判断是正数还是负数。
32 位整数的最大值为 0x7fffffff,如果 num1 <= 0xfffffff,就返回本身;否则,返回原来负数的值,也就是 ~(num1^0xffffffff)
参考 https://blog.csdn.net/lrs1353281004/article/details/87192205