原码、反码和补码
在计算机系统中,数据的表示和运算是最基础的问题。原码、反码和补码是三种不同的二进制编码方式,它们各有特点,在计算机发展史上发挥了重要作用。
二进制计算
十进制转二进制
将十进制数转换为二进制数的常用方法主要有两种,分别是“除 2 取余法”和“乘 2 取整法”。以下是这两种方法的详细说明。
1. 除 2 取余法
这种方法适用于将十进制整数转换为二进制。具体步骤如下:
- 除以 2:将十进制数除以 2,记录下余数。
- 继续除以 2:用商继续除以 2,重复上一步骤,直到商为 0。
- 逆序排列:将记录的余数从最后到最前排列,得到的就是对应的二进制数。
示例
例如,将 17 转换为二进制:
- 17 \div 2 = 8 余数 1
- 8 \div 2 = 4 余数 0
- 4 \div 2 = 2 余数 0
- 2 \div 2 = 1 余数 0
- 1 \div 2 = 0 余数 1
将余数逆序排列,得到 (10001)_2。
2. 乘 2 取整法
这种方法适用于将十进制小数转换为二进制。具体步骤如下:
- 乘以 2:将十进制小数乘以 2,记录下整数部分。
- 继续乘以 2:对剩下的小数部分继续乘以 2,重复上一步骤,直到小数部分为 0 或达到所需精度。
- 顺序排列:将记录的整数部分按顺序排列,得到的就是对应的二进制小数。
示例
例如,将 0.625 转换为二进制:
- 0.625 \times 2 = 1.25 (取出整数部分 1)
- 0.25 \times 2 = 0.5 (取出整数部分 0)
- 0.5 \times 2 = 1.0 (取出整数部分 1)
将取出的整数部分按顺序排列,得到 (0.101)_2。
合并整数和小数
如果需要将一个包含整数和小数的十进制数(如 173.8125)转换为二进制,可以分别处理整数部分和小数部分,然后将结果合并:
- 整数部分 173 转换为二进制得到 (10101101)_2
- 小数部分 0.8125 转换为二进制得到 (0.1101)_2
最终合并结果为 (10101101.1101)_2。
这种方法简单易学,是进行十进制与二进制互转的重要技巧。
二进制转十进制
将二进制数转换为十进制数的计算方式主要有以下几种方法:
方法一:权重法
- 从右到左:将二进制数的每一位从右到左依次标记为 0、1、2、3……(即位数)。
- 乘以 2 的幂:每一位上的数字乘以 2 的对应次方(从 0 开始)。
- 求和:将所有结果相加,得到的就是对应的十进制数。
示例
例如,将二进制数 (1101)_2 转换为十进制:
- 第 0 位: 1 \times 2^0 = 1
- 第 1 位: 0 \times 2^1 = 0
- 第 2 位: 1 \times 2^2 = 4
- 第 3 位: 1 \times 2^3 = 8
将这些结果相加:
因此,(1101)_2 = (13)_{10}。
方法二:逐位累加法
这种方法是另一种简单有效的计算方式:
- 从左到右:从最高位开始,逐位处理。
- 累加:每次将当前结果乘以 2,然后加上当前位的值(0 或 1)。
示例
以二进制数 (1101)_2 为例:
- 初始值为 0:
- 第一位(1): 0 \times 2 + 1 = 1
- 第二位(1): 1 \times 2 + 1 = 3
- 第三位(0): 3 \times 2 + 0 = 6
- 第四位(1): 6 \times 2 + 1 = 13
最终结果同样为 (1101)_2 = (13)_{10}。
小数部分转换
如果需要将包含小数的二进制数(如 (101.101)_2)转换为十进制,可以分别处理整数部分和小数部分:
整数部分
使用上述方法进行转换。
小数部分
从小数点后第一位开始,每一位乘以 2^{-n}(n 为该位的位置,从 1 开始)。
示例
对于二进制数 (101.101)_2:
- 整数部分 (101)_2 = (5)_{10}
- 小数部分:
- 第一位: 1 \times 2^{-1} = 0.5
- 第二位: 0 \times 2^{-2} = 0
- 第三位: 1 \times 2^{-3} = 0.125
小数部分相加:
最终结果为:
通过以上方法,可以有效地将二进制数转换为十进制数。
原码
原码(Original Code)是最简单直观的带符号数表示方法,它保留了数值的自然特性,使得人类容易理解和判断数值的大小。在原码中,最高位表示符号,其余位表示数值的绝对值。
原码的表示方法
- 符号位:用 0 表示正数,1 表示负数
- 数值位:直接使用二进制的绝对值表示
原码的特点
- 表示范围:对于 n 位二进制数(包含符号位):
- 最大正数:+2^{n - 1} - 1
- 最小负数:-(2^{n - 1} - 1)
- 例如:8 位原码的表示范围是 -127 ~ +127
- 存在两个零:
- +0:
00000000
(符号位为 0) - -0:
10000000
(符号位为 1)
- +0:
- 直观性:
- 符号位与数值分开,易于理解
- 数值部分直接表示绝对值
- 便于人工判断数值大小
原码示例
以 8 位二进制为例:
- +5:
00000101
(0|000 0101) - -5:
10000101
(1|000 0101) - +0:
00000000
(0|000 0000) - -0:
10000000
(1|000 0000) - +127:
01111111
(最大正数) - -127:
11111111
(最小负数)
原码的运算规则
- 加法运算:
- 同号数相加:
- 符号位保持不变
- 数值位直接相加
- 注意可能发生溢出
- 异号数相加:
- 比较两数绝对值大小
- 用大数减去小数的绝对值
- 结果符号位取绝对值大的数的符号位
- 同号数相加:
- 减法运算:
- 转换为加上被减数的相反数
- A - B = A + (-B)
- 相反数:改变符号位,数值位不变
原码运算示例
- 同号数相加:
(+5) + (+3) 00000101 + 00000011 1. 同号,符号位保持为0 2. 数值位:000 0101 + 000 0011 = 000 1000 结果为:00001000 (+8)
- 异号数相加:
(+5) + (-3) 00000101 + 10000011 1. |5| > |3|,结果为正 2. 5 - 3 = 2 结果为:00000010 (+2)
- 负数相加:
(-5) + (-3) 1. 两个负数相加,结果为负 2. |5| + |3| = 8 3. 所以结果为 -8 结果为:10001000 (-8)
- 溢出示例:
(+120) + (+10) 01111000 + 00001010 1. 数值相加:130 > 127 2. 超出表示范围 结果:溢出错误
原码的特点
- 运算需要特殊处理:
- 需要判断符号和比较绝对值大小
- 需要根据不同情况采用不同的运算规则
- 需要额外的硬件电路处理符号位
- 存在两个零表示:
- +0:
00000000
- -0:
10000000
- 这种双重表示增加了判零操作的复杂性
- +0:
- 运算规则复杂:
- 不能直接进行二进制加减运算
- 需要根据符号位分别处理
- 硬件实现相对困难
为什么需要改进
原码虽然直观易懂,但在实际应用中存在一些不便:
- 运算效率:
- 需要额外的判断和比较步骤
- 硬件实现较为复杂
- 运算速度较慢
- 存储效率:
- 双重零表示占用了额外编码空间
- 浪费了一个编码位置
- 运算复杂度:
- 需要不同的运算规则
- 实现相对复杂
- 容易出错
这些问题促使了反码和补码的发展,特别是补码因其优良的特性成为现代计算机中最常用的数字表示方法。
反码
反码(One's Complement)是一种改进的带符号数表示法,通过对负数的特殊处理,使得加减运算更加统一。它是补码表示的一个中间步骤。
反码的表示方法
- 正数:与原码相同
- 负数:符号位为 1,数值位按位取反(0 变 1,1 变 0)
反码的特点
- 表示范围:与原码相同
- 最大正数:2^{n - 1} - 1
- 最小负数:-(2^{n - 1} - 1)
- 存在两个零:
- +0:
00000000
- -0:
11111111
- +0:
- 运算特性:
- 负数可以通过按位取反得到
- 加法需要进行端进位处理
- 减法可转化为加法运算
反码的运算规则
- 加法运算:
- 两数相加,若有进位,需要末位加 1(循环进位)
- 循环进位是为了处理两个零的问题
- 减法运算:
- 转换为加上被减数的反码
- A - B = A + (-B)的反码
- 负数的反码等于对应正数按位取反
- 循环进位处理:
- 如果最高位产生进位,将进位加到最低位
- 这种处理可以避免出现两个不同的零
反码运算示例
- 同号数相加:
(+3) + (+2) 00000011 + 00000010 1. 同号正数相加,符号位保持为 0 2. 数值位直接相加 结果为:00000101 (+5)
- 异号数相加:
(+3) + (-2) 00000011 + 11111101 1. 两数相加:11111110 2. 有进位 1,末位加 1(循环进位) 3. 11111110 + 00000001 = 00000001 结果为:00000001 (+1)
- 负数相加:
(-3) + (-2) 11111100 + 11111101 1. 两数相加:11111001 2. 有进位 1,末位加 1(循环进位) 3. 11111001 + 00000001 = 11111010 结果为:11111010 (-5)
- 零值相加:
(+0) + (-0) 00000000 + 11111111 1. 两数相加:11111111 2. 有进位 1,末位加 1(循环进位) 3. 11111111 + 00000001 = 00000000 结果为:00000000 (+0)
反码的应用场景
- 数值取反:
- 通过按位取反实现数值取反
- 便于硬件电路实现
- 过渡到补码:
- 反码是补码的理论基础
- 补码可以通过反码加 1 得到
为什么需要改进
反码虽然简化了加减运算,但仍存在以下问题:
- 运算规则复杂:
- 需要考虑末位进位
- 需要额外的硬件电路处理进位
- 循环进位增加了运算复杂度
- 零值的两种表示:
- +0 和 -0 共存
- 增加了判零操作的复杂性
- 浪费了一个编码位置
- 硬件实现困难:
- 循环进位需要专门的处理电路
- 增加了硬件成本和复杂度
这些问题最终导致了补码的出现和广泛应用。
补码
补码(Two's Complement)是现代计算机中最常用的带符号数表示方法。它解决了原码和反码中的主要问题,使得加减运算完全统一,硬件实现简单。
补码的表示方法
- 正数:与原码相同
- 负数:反码加 1
补码的特点
- 表示范围:对于 n 位二进制数:
- 最大正数:2^{n - 1} - 1
- 最小负数:-2^{n - 1}
- 唯一的零表示:
- 0:
00000000
- 0:
- 对称性:除了最小负数外,任何数的相反数都可以用补码表示
补码的运算规则
- 加法:
- 直接相加,舍弃进位
- 如果最高位有进位,表示溢出
- 减法:
- A - B = A + (-B)
- (-B)的补码 = B的补码取反加 1
补码示例
以 8 位二进制为例:
- +5:
00000101
- -5:
11111011
- 0:
00000000
- -128:
10000000
(最小负数) - +127:
01111111
(最大正数)
补码运算示例
- 同号数相加:
(+3) + (+2) 00000011 + 00000010 = 00000101 (+5) 1. 同号正数相加,符号位保持为0 2. 数值位直接相加 结果为:00000101 (+5)
- 异号数相加:
(+3) + (-2) 00000011 + 11111110 1. 两数直接相加:00000001 2. 无需特殊处理 结果为:00000001 (+1)
- 负数相加:
(-3) + (-2) 11111101 + 11111110 1. 两数直接相加:11111011 2. 最高位进位被舍弃 结果为:11111011 (-5)
- 零值运算:
(+1) + (-1) 00000001 + 11111111 1. 两数直接相加:100000000 2. 舍弃最高位进位 结果为:00000000 (0)
- 溢出示例:
(+127) + (+1) 01111111 + 00000001 1. 两数相加:10000000 2. 正数相加得到负数,发生溢出 结果为:10000000 (-128,溢出)
补码的特点
- 运算规则统一:
- 直接相加即可
- 不需要额外的进位处理
- 溢出判断简单直观:
- 正数相加得到负数,发生溢出
- 零只有一种表示方式:
- 0:
00000000
- 0:
三种编码的比较
编码方式 | 零的表示 | 运算便利性 | 范围对称性 | 硬件实现难度 |
---|---|---|---|---|
原码 | 两个零 | 复杂 | 对称 | 较难 |
反码 | 两个零 | 较简单 | 对称 | 较难 |
补码 | 一个零 | 简单 | 不对称 | 简单 |
总结
- 原码:最直观,但运算复杂
- 反码:过渡方案,简化了某些运算
- 补码:最优选择,现代计算机的标准