Skip to content
On this page

1. 为什么学习机器数、真值、原码、反码、补码

在计算机行业,一直都需要和这三个打交道,他们出现在各个方方面面,例如UDP协议包的校验和,算法中的按位 与 按位或 等等。理解他们的区别和作用,有利于巩固知识架构基础。原码、反码、补码都是存储一个具体数据的表示形式。
本文主要目的:了解原码、反码、补码的诞生原因,各自解决了什么问题。

2. 机器数、真值

在学习原码、反码、补码前,先理解一些基础概念。

2.1 机器数:

一个数在计算机中以二进制形式表示,称为这个数的机器数。
机器数包含原码、反码、补码三种表示形式。
机器数带符号位,最高位表示符号,0正1负。

sh
# 8位 2进制

十进制: -3
机器数: 1000 0011  这里是原码的表示形式

2.2 真值:

带符号位的机器数对应真正数值称为真值,即我们日常使用的十进制。

sh
# 8位 2进制

机器数: 1000 0011
真值: -3

2.3 形式值:

无符号位,全都是值

sh
# 8位 2进制

机器数: 1000 0011
形式值: 131 

2.4 为什么机器数要带符号位

在计算机中,只有加法运算器,没有减法运算器。所以减法的实现,只能通过加一个负数来实现。
例如 1 - 1 => 1 + (-1)
我们需要有负数的概念,所以引入了符号位。
原码、反码、补码都是带符号位的。

一些位操作可以将其当成无符号位,例如 >>> 无符号右移。 9 >> 2 = -3; -9 >>> 2 = 1073741821

3. 原码、反码、补码

机器数包含原码、反码、补码三种表示形式。
在后面例子中,使用以下表示
[+1]原 = [0000 0001]原
左边为十进制,右边为对应的机器数。

3.1 原码

sh
# 8位 2进制

[+1]原 = [0000 0001]
[-1]原 = [1000 0001]

原码是最简单的机器数表示形式,原码的产生,是为了能够表示负数,使计算机有负数的概念。
原码:符号位 + 真值绝对值的二进制
最高位表示符号,0 正 1 负。其他位为值。

在计算时,原码的符号位不参与计算,数值位进行相加。
在纯正数、纯负数的相加中,原码可以表示为期待值。但在正数相减时,结果不如人意。

sh
# 纯正数
# [+1]原 + [+2]原 = [+3]原 | 期待值
    [+1] + [+2]原
=   [0000 0001]原
  + [0000 0010]原
  
=   [0000 0011]原
=   [+3]原


# 纯负数 
# [-1]原 + [-2]原 = [-3]原 | 期待值(原码的符号位不参与计算,数值位进行加减)
    [-1] + [-2]原
=   [1000 0001]原
  + [1000 0010]原
  
=   [1000 0011]原
=   [-3]原


# 正数相减
# [+2]原 + [-1]原 = [-3]原 | 非期待值
    [+2] + [-1]原
=   [0000 0010]原
  + [1000 0001]原
  
=   [1000 0011]原
=   [-3]原


# [+1]原 + [-1]原 = [-2]原 | 非期待值
    [+1] + [-1]原
=   [0000 0001]原
  + [1000 0001]原
  
=   [1000 0010]原
=   [-2]原

原码引入了符号位,有了负数概念,可以实现负数相加,但有遗留问题

  1. 无法实现正数相减。原码的正数相减,相当于两个正数相加后,再转换为负值

接下来看反码,是如何进一步解决问题的。

3.2 反码

sh
# 8位 2进制

[+1]反 = [0000 0001]
[-1]反 = [1111 1110]

反码:

  • 正数的反码是其原码。
  • 负数的反码,符号位不变,其他位按位取反。

在计算时,符号位与数值一起参加运算,反码的符号位相加后,如果最高位有进位出现,则要把它送回到最低位去相加。
正数相减,反码可以表示为期待值了。解决了原码遗留的正数相减问题,但一个数和其相反数相加产生了 -0 值。

sh
# 在以下计算中转换成原码,是因为原码对我们大脑来说是最容易理解的,看到原码便知道真值是多少。 

# 纯正数
# [+1]反 + [+2]反 = [+3]反 | 期待值
    [+1] + [+2]反
=   [0000 0001]反
  + [0000 0010]反
  
=   [0000 0011]反
=   [+3]反


# 纯负数 
# [-1]反 + [-2]反 = [-3]反 | 期待值
# 反码符号位与数值一起参加运算,反码的符号位相加进位,需要往最低位进1
    [-1] + [-2]反
=   [1111 1110]反
  + [1111 1101]反
  
=   [1111 1100]反
=   [1000 0011]原
=   [-3]反


# 正负混合
# [+2]反 + [-1]反 = [+1]反 | 期待值
    [+2] + [-1]反
=   [0000 0010]反
  + [1111 1110]反
  
=   [0000 0001]反
=   [0000 0001]原
=   [+1]反


# [+1]反 + [-1]反 = [-0]反 | 非期待值 (1 - 1 结果为 -0)
    [+1] + [-1]反
=   [0000 0001]反
  + [1111 1110]反
  
=   [1111 1111]反
=   [1000 0000]原
=   [-0]反

反码实现了正数相减,但遗留1个问题

  1. 一个数和其相反数相加,产生 -0 值

接下来看补码,是如何进一步解决问题的。

3.3 补码

sh
# 8位 2进制

[+1]补 = [0000 0001]
[-1]补 = [1111 1111]

补码:

  • 正数的补码是其原码。
  • 负数的补码,在反码的基础上 +1。

补码设计的初衷:为了能够让一个数和其相反数相加,通过溢出从而使计算机内计算结果变为0的二进制码。“反码加一”只是补码所具有的一个性质,不能被定义成补码。
在反码中 数 + 其相反数 = 1111 1111,提前对负数进行 + 1,便会产生溢出。

sh
# 在反码中,一个数加上其相反数。
[3]反 + [-3]
=   [0000 0011]反
  + [1111 1100]反

=   [1111 1111]反


# 补码便是利用其 +1 进位为 1 0000 0000,最高位溢出变为 0
    [1111 1111]
  + [0000 0001]反
= [1 0000 0000]反

在计算时,符号位与数值一起参加运算,反码的符号位相加后,如果符号位有进位出现,忽略 一个数和相反数相加,补码可以表示为期望值了。解决了反码遗留的数和相反数相加得到 -0 问题

以下代码可以选择性看,重复性高,只要理解补码解决的问题即可。

sh
# 在以下计算中转换成原码,是因为原码对我们大脑来说是最容易理解的,看到原码便知道真值是多少。 

# 纯正数
# [+1]补 + [+2]补 = [+3]补 | 期待值
    [+1] + [+2]补
=   [0000 0001]补
  + [0000 0010]补
  
=   [0000 0011]补
=   [0000 0011]反
=   [0000 0011]原
=   [+3]补


# 纯负数 
# [-1]补 + [-2]补 = [-3]补 | 期待值
# 补码符号位与数值一起参加运算,补码的符号位相加进位,忽略进位
    [-1] + [-2]补
=   [1111 1111]补
  + [1111 1110]补
  
=   [1111 1101]补
=   [1111 1100]反
=   [1000 0011]原
=   [-3]补


# 正负混合
# [+2]补 + [-1]补 = [+1]补 | 期待值
    [+2] + [-1]补
=   [0000 0010]补
  + [1111 1111]补
  
=   [0000 0001]补
=   [0000 0001]原
=   [+1]补


# [+1]补 + [-1]补 = [0]补 | 期待值
    [+1] + [-1]补
=   [0000 0001]补
  + [1111 1111]补
  
=   [0000 0000]补
=   [0000 0000]原
=   [0]补

到此,补码实现了正数相减,也解决了 -0。

3.4 公式式推导

4. 机器数的范围

由于最高位是符号位,取值范围和无符号位有所区别。
以下是无符号和有符号表示的范围区别:

sh
# 8位 二进制
# 无符号位 称为形式值

# 无符号: 
最大 1111 1111
    形式值: 255
最小 0000 0000
    形式值: 0

范围: [0, 255]


# 带符号: 
最大 0111 1111
    真值: 127
最小 1111 1111
    真值: -127

范围: [-127, 127]

总结

  1. 为了实现相减,需要有负数的概念。便有了原码,引入了符号位,可以表示负数。实现了一部分相减,纯负数相加,等价于 负数减去一个正数。但依旧没有完全解决相减的问题,当正数加负数时,得到的结果非期待值。即没法解决正数相减问题
  2. 为了解决正数相减的问题,产生了反码。除了相反数,反码使得正数相减为期望值,但出现了个新的问题,在两数为相反数时,相加为 -0。
  3. 为了解决 -0 问题,产生了补码。补码的设计初衷就是为了使相反数相加后,通过溢出从而使计算机内计算结果变为 0。至此,解决了计算机的相减问题。负数在计算机中以补码的形式存在,便于计算。
- 发送端:
    - 校验和字段置0
    - 通过在UDP头前添加12位的伪头部(有目标IP和原IP,除了伪头部整个UDP长度)。    
    - 伪头部和UDP用户数据报以16位(2字节)划分,不够2字节补0。
    - 将这些2字节取反码后求和,和再取反码后,存放在校验和字段
    - 丢弃伪头部和补0字节,提交给Ip封装

- 接收端:
    - 在UDP头前添加伪头部
    - 按2字节划分,不够2字节补0。
    - 将这些2字节取反码后求和
    - 如果都是1,数据没有差错,丢弃伪头部和补0字节,提交给应用层
    - 否则,该请求丢弃或错误反馈给应用层    

- 原理:
        (发送端原值 + 校验和)取反和

    1. 传给接收端,对比原值,仅多了个校验和
    2. 接收端取反后求和,对比发送端原值取反求和,多了个校验和的反码
    3. 校验和的反码:由发送端原值取反求和得到。
    4. 所以接收端在第2步进行求和     
    本质:发送端原值取反求和  加  发送端原值取反求和
    5. 数据无误情况下    
    接收端原值:发送端原值 +  校验和

例子:     
```js
发送端:
原     11  11  00  11
取反 00 00 11  00
和 11
和反  00
传给接收端 11 11 00 11

接收端:
原     11  11  00  11
取反 00 00 11 00
和 11
js
发送端:     
00 00 00 00
取反 11  11  11  11
00 
和反 11
传给接收端 00 00 11 00

接收端:    
00 00 11 00
取反 11  11  00 11
11