← 返回博客数制

BCD(二进制编码十进制):基础详解

BCD 用 4 位编码每个十进制数字。了解压缩和非压缩形式、加法中的 +6 校正、十进制缩放及实际权衡。

DennyDenny
阅读约需 9 分钟
Chalkboard feature illustration for “BCD(二进制编码十进制):基础详解”

简述: 二进制编码十进制(BCD)用一个 4 位半字节存储每个十进制数字 0–9,编码 1010–1111 不使用。当未经校正的二进制和大于 9 或产生进位输出时,BCD 一位加法器执行+6 校正。BCD 能精确保存已编码的十进制数字,但有限精度的除法与定标仍需要明确的舍入规则。

二进制与十进制位值的质因数不同,因此许多有限十进制小数——包括 0.1——都不存在有限的二进制展开。二进制编码十进制直接存储十进制数字,因而可避免初始的二进制小数表示误差;但当运算结果所需位数超出格式容量时,可靠的应用仍须明确定标、精度和舍入规则。

什么是二进制编码十进制?

BCD 使用各自独立的四位二进制码表示十进制数字 0 到 9。四位可编码 16 种模式,但 BCD 只使用前十种:

十进制BCD 半字节
00000
10001
20010
30011
40100
50101
60110
70111
81000
91001

剩下的六种模式 1010 到 1111——在纯二进制中即数值 10 到 15——在 BCD 中是无效的。产生其中一种模式的电路,要么发生了数字溢出,要么接收到格式错误的输入;BCD 加法器必须检测并校正这种情况。

多位十进制数由多个半字节串联编码。例如,数字 327 表示为:

32710  =  0011⏟3  0010⏟2  0111⏟7327_{10} \;=\; \underbrace{0011}_{3}\;\underbrace{0010}_{2}\;\underbrace{0111}_{7}

与之相比,纯二进制表示 32710=1010001112327_{10}=101000111_2 使用 9 位而非 12 位。BCD 用额外位数换取以数字为中心的表示方式,并简化某些十进制接口。

压缩 BCD 与非压缩 BCD

有两种常见的存储布局。

非压缩 BCD每字节存储一个十进制数字。低半字节保存数字;高半字节为零,或携带类似 ASCII 的标记(早期 IBM 和 x86 BCD 指令中为 0x30,因而可直接生成可打印的 ASCII 数字)。这种格式仅利用一半存储位,但输出为字符十分直接。

压缩 BCD每字节存储两个十进制数字,每个半字节各存一个。数字 327 的压缩 BCD 需要两个字节:0x03 0x27 。压缩 BCD 的密度是前者两倍,也是硬件 BCD 加法器和 x86 DAA/DAS 指令所使用的格式。

格式数字 327字节数密度
非压缩0x03 0x02 0x0731 个数字/字节
压缩0x03 0x2722 个数字/字节
纯二进制(16 位)0x01 0x472完整范围

对于可装入 16 位的数值(最大 65,535),纯二进制与压缩 BCD 的密度相同,却能编码 65,536 个不同数值,而不是 10,000 个。只有在必须执行精确匹配十进制的算术时,成本才会再次显现。

BCD 为什么仍然存在?

若二进制更密集,为何还要使用 BCD?有三个原因使 BCD 得以延续。

精确存储十进制数字。 十进制的 0.1 没有终止的二进制表示。当十进制小数点位置是格式的一部分时,BCD 可以精确存储 0.1 。只有当数学结果适合所选数字位数和小数位时,算术才是精确的;除法及其他非终止结果仍需要舍入。IEEE 754 浮点数介绍了这种权衡的二进制一面。

显示转换简单。 数字时钟或计算器可通过小型查找译码器,将每个 BCD 半字节转换为七段显示器的驱动信号,无需执行除以 10、循环减法或软件格式转换。后文介绍的 BCD 转七段译码器正是这一用途。

十进制业务规则。 金融和计量应用通常规定十进制小数位和舍入方式。压缩十进制、COBOL COMP-3 、十进制浮点数和缩放二进制整数都是可行的表示法。当数字级存储或接口很重要时,BCD 很有用;法规并不普遍强制使用 BCD。

BCD 数字相加:+6 校正

BCD 加法使用普通的四位二进制加法器——可参阅四位行波进位加法器文章复习——然后接一个十进制校正阶段,用于修正大于 9 的结果。

规则很简单。将两个 BCD 数字当作四位二进制数相加,然后:

  • 若二进制和小于或等于 9,并且没有进位输出,则 BCD 结果保持该二进制和不变。
  • 若二进制和大于 9,或四位加法器产生了进位输出,则加 6(0110)进行校正,并向下一数字传递进位。

为何是 6?因为 BCD 跳过了六种无效模式 1010–1111。落在该范围内或超出该范围的任何二进制和,在 BCD 编号中都恰好超前了六个位置;加 6 会使它绕回,并产生十进制算术所期望的进位。

例题:7 + 5

对操作数编码并按四位二进制相加:

7=01115=010112=1100\begin{aligned} 7 &= 0111 \\ 5 &= 0101 \\ \hline 12 &= 1100 \end{aligned}

结果 1100 在二进制中是 12,属于无效 BCD 模式之一。应用 +6 校正:

1100  (12)+  0110  (6)1 0010\begin{aligned} 1100 \;(12) \\ +\;0110 \;(6) \\ \hline 1\,0010 \end{aligned}

校正后的结果为 0001 0010,即 12 的 BCD 表示——十位上的进位和个位上的 2。这正是十进制算术应产生的结果。

例题:8 + 9

8=10009=100117=1 0001\begin{aligned} 8 &= 1000 \\ 9 &= 1001 \\ \hline 17 &= 1\,0001 \end{aligned}

四位加法器产生 0001 并带有进位输出。即使 0001 看似有效的 BCD 数字,进位输出仍会触发 +6 校正:

0001+  01100111\begin{aligned} 0001 \\ +\;0110 \\ \hline 0111 \end{aligned}

最终结果为 0001 0111,即 17 的 BCD 表示。若不校正,串接原始进位与低半字节将得到 0001 0001,BCD 译码器会将其读为 11,而不是 17。

“需要校正”的通用检测条件为:

adjust  =  Cout  ∨  (S3∧S2)  ∨  (S3∧S1)\text{adjust} \;=\; C_{\text{out}} \;\lor\; (S_3 \land S_2) \;\lor\; (S_3 \land S_1)

其中 S3S2S1S0S_3 S_2 S_1 S_0 是四位二进制和。两个 AND 项合起来覆盖六种无效模式 1010–1111,而 CoutC_{\text{out}} 捕捉和已绕过 15 的情况。

硬件:单数字 BCD 加法器

单数字 BCD 加法器由两个四位二进制加法器和一小段校正检测电路构成:

  A[3:0]  B[3:0]
     |      |
     v      v
 +-----------------+
 |  4-bit adder #1 |  <-- ordinary binary add (see /docs/adder)
 +-----------------+
   S[3:0]   C_out
     |        |
     +--+--+--+
        |  |
        v  v
   +----------+
   |  detect  |  --> adjust = C_out OR (S3·S2) OR (S3·S1)
   +----------+
        |
        v       0110 if adjust, else 0000
 +-----------------+
 |  4-bit adder #2 |  <-- adds the correction
 +-----------------+
        |
        v
     BCD result, BCD carry-out

第一个加法器执行原始二进制加法。检测模块计算 adjust 信号。第二个加法器无条件地向中间和加上 0 或 6。BCD 进位输出就是 adjust 信号本身:每当需要加 6 时,也会向下一个十进制数字传递一个 1。将 nn 个这样的模块菊花链连接,就得到一个 nn 位 BCD 行波加法器。该结构类似二进制全加器与半加器链,只是数字更宽,且多了校正阶段。

此类电路可由两个加法器模块和校正检测逻辑构成,再由数字显示器显示结果。输入应限制为有效的 BCD 数字 0–9;该校正表达式不能作为无效输入半字节的通用纠错逻辑。

BCD 减法

减法遵循相同模式,但以9 的补码或10 的补码代替二进制的 2 的补码。计算 A−BA - B 时:

  1. 将每个数字 dd 替换为 9−d9 - d,构造 BB 的 9 的补码。
  2. 将结果作为 BCD 加法加到 AA 。
  3. 若出现最终进位输出,向结果加 1(10 的补码校正)。该进位表示结果为正。
  4. 若没有进位输出,结果为负;重新取补码以恢复其绝对值。

9 的补码之于十进制,如同 1 的补码之于二进制;10 的补码则是 2 的补码的十进制对应物。BCD ALU 只需在减法的另一侧使用相同的校正阶段。

BCD 的缺点

其优点伴随着实际成本。

存储开销。 每个十进制数字需要 4 位,因此 4 个字节最多编码 99,999,999。相同的 32 位若采用纯二进制,可表示最高约 42.9 亿的数值,其数值范围约为前者的 43 倍。对于科学计算或图形处理,BCD 的存储效率通常较低。

更多算术工作。 每次数字相加都需要二进制加法、校正检测和校正。若干旧版 x86 十进制校正指令在 64 位模式中不可用。压缩十进制、十进制浮点数还是缩放整数表现最佳,取决于 ISA、工作负载、库和所需语义。

没有原生小数支持。 这里描述的 BCD 编码整数。小数 BCD 需要按约定固定十进制点位置,或改用密集压缩十进制或 IEEE 754-2008 十进制浮点数;后两者以更紧凑的代码将数字每三位一组编码。这些编码的历史可追溯到最早的二进制数制,以及关于以十还是以二计数的长期争论。

BCD 仍存在哪些地方

只要输入与输出是十进制,BCD 就依然存在,即使计算本身未必如此。

  • 数字时钟和手表。 一只 24 小时时钟将时、分、秒存储为六个 BCD 数字。计数器芯片递增秒数;小型比较器检测到 60 时滚入分钟;同样模式处理 60 分钟和 24 小时。每个数字都经由 BCD 至七段译码器驱动七段 LED。
  • 计算器。 以数字为中心的算术可简化十进制输入与显示,尽管计算器实现也会使用二进制和其他内部表示。
  • 工业控制面板。 拇指轮开关直接输出 BCD,PLC 输入无需转换即可读取 BCD。
  • 企业系统。 支持压缩十进制的体系结构和语言,对于固定小数位业务数据和 COBOL 工作负载仍有实用价值。
  • 仪表与销售点设备。 十进制显示和规定的舍入规则可使数字导向格式更方便,尽管具体实现选择有所不同。

这一教训可以推广。当问题领域天然是十进制时,显式编码数字往往优于在每次运算中进行进制转换。这一逻辑也支持今天在货币处理软件中使用定点整数算术,即使底层 CPU 完全按二进制工作。

在仿真器中验证

一项清晰的练习是:构建单数字 BCD 加法器,并观察校正如何发生。

  1. 将两个四位输入寄存器 AA 与 BB 接入一个加法器模块,显示原始五位和。
  2. 构建检测逻辑:对 CoutC_{\text{out}} 、(S3∧S2)(S_3 \land S_2) 和(S3∧S1)(S_3 \land S_1) 进行 OR。
  3. 使用检测信号将常数 0110 门控到第二个加法器,并与第一个和相加。
  4. 将校正结果的低半字节接到数字显示器,再将 BCD 进位输出接到单独的一位显示器。
  5. 扫描 A=0..9A = 0..9 与 B=0..9B = 0..9 。确认两位显示器显示 0–18,且永远不会出现 A–F 符号。

对于更宽的数值,串接第二个 BCD 加法器模块。第一个模块的进位输出成为第二个模块的进位输入,就像二进制行波链一样。

后续阅读

单数字 BCD 加法器需要配合可读的显示结果。BCD 转七段译码器:从数字到显示从 BCD 真值表推导各段驱动信号的布尔表达式,并在此前介绍的译码器基础上说明如何连接实际显示器。IEEE 754 浮点数则从二进制表示角度说明二进制小数的设计依据,以及金融软件持续采用十进制表示的原因。

打开四位二进制计数器模板,为 BCD 加法器提供递增输入,并观察每逢第十个时钟节拍触发校正——这是观察+6 效果最直接的方式。

将想法变成真正可运行的电路。