拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

8086汇编BCD码:六条调整指令与十进制加法进位链

8086汇编BCD码:六条调整指令与十进制加法进位链

九加八等于十七,这话连小学生都不会说错。可我第一次用汇编写十进制加法显示程序的时候,屏幕上打出来的结果是 11。原因不复杂:CPU 只会做二进制加法,08H 加 09H 得到的是 11H,这个数在二进制语境下确实是 17,可它按十进制读出来就是 11。想让屏幕上出现 17,中间必须人为插一步修正,这一步就是汇编里常说的 BCD 码调整指令。这篇笔记把 BCD 码的两种存储形态、DAA、DAS、AAA、AAS、AAM、AAD 这六条调整指令的完整逻辑、多位十进制加法的进位链写法,以及我在实际调试里反复踩过的坑,从头到尾捋一遍。如果你正在学 8086 汇编、在啃《IBM-PC 汇编语言程序设计》那类教材,或者做嵌入式仪表显示、需要精确十进制运算,这篇内容可以直接当操作手册用;哪怕你只是想弄明白为什么现代编译器几乎不用这些指令,也能找到答案。

1. 从一次加法算错说起:BCD码到底解决什么问题

1.1 为什么二进制加法会"算错"十进制

很多人第一次接触 BCD 码都会犯迷糊:明明用二进制存数,为什么还要专门搞一套十进制编码?答案藏在两个世界的缝隙里。CPU 的加法器是二进制加法器,它只认 0 和 1,做的是按位加加进位。08H 加 09H,二进制是 0000 1000 加 0000 1001,结果是 0001 0001,也就是 11H。如果你把这个 11H 当成十六进制数看,它是 17;可如果你希望这个字节的每一个 4 位组分别表示一位十进制数字,那它读出来就是"11",因为高 4 位的 1 表示十位是 1,低 4 位的 1 表示个位是 1。问题就出在这里——十进制加法在个位满十时需要向十位进 1,而二进制加法在低 4 位满十六时才向高 4 位进 1,两种进位阈值差了 6,这 6 就是所有调整指令存在的理由。

我第一次真正理解这件事,是在纸上把 09H 加 09H 一步步展开。二进制算出来是 0001 0010,即 12H,低 4 位的 2 是对的,但高 4 位多出来一个 1,这个 1 是二进制进位带来的,它在十进制里没有意义,因为 9 加 9 等于 18,十位应该是 1,个位应该是 8,正确结果是 18H。对比之下差值正好是 6,这就是 DAA 在低 4 位大于 9 时给 AL 加 6 的来源。理解了"差 6"这个核心,六条调整指令就不再是六条需要死记硬背的规则,而是一套围绕 6 做加减的补偿机制。

1.2 压缩BCD与非压缩BCD的两套玩法

BCD 码的存储方式有两种,这个分类非常关键,因为它直接决定你该用哪条调整指令。压缩 BCD,也叫组合 BCD,把一个字节拆成两半,高 4 位存十位,低 4 位存个位,一个字节正好存两位十进制数。比如十进制 47,压缩 BCD 就是 0100 0111,写作 47H——注意它长得像十六进制的 47,但含义完全不同。非压缩 BCD,也叫分离 BCD,一个字节只存一位十进制数字,高 4 位通常填 0,低 4 位放数字。十进制 47 用非压缩 BCD 表示就是两个字节:04H 和 07H。前者省空间,后者好处理,两条路线在加减乘除上各配一套调整指令,用错了就会得到莫名其妙的结果。

压缩 BCD 的优势是存储密度高,一个字节两位数字,做多位十进制运算时非常紧凑,早期的财务软件、电子秤、计价器大量使用这种方式。代价是每条字节里高低 4 位要分别判断,调整逻辑稍微绕一点。非压缩 BCD 的优势是每一位单独占一个字节,做加法时进位可以直接往高位字节传,写循环很顺手,缺点是一半的字节空间被浪费。我在做仪表数码管显示的项目里,内部运算用压缩 BCD 省 RAM,往数码管送数据前再转成非压缩 BCD 逐位输出,两套形态混用是常态,关键是每一步都得清楚自己手上这个字节是哪一种。

1.3 非法编码:那些没被定义的4位组合

BCD 码的合法值只有 0000 到 1001 这十个,1010 到 1111 这六个组合在 BCD 里是非法的。这个细节听起来像是考试题,实际调试时却是最常见的坑。假如一次 DAA 之后 AL 的高 4 位变成了 1010,说明你的调整逻辑没走对,或者你根本不该用 DAA 而是该用 AAA。我遇到过一种情况:把两个压缩 BCD 数相加,结果里出现了 0FH 这种字节,怎么看都不像十进制,最后发现是加法之后忘了调整,二进制进位把低 4 位推到了 15。

非法编码还有一个隐蔽来源,就是加减运算本身产生的中途状态。DAA 的内部逻辑是先判断低 4 位、加 6,再判断高 4 位、加 60H,两次判断之间有中间态,中间态可能短暂落在非法区间里,但最终结果一定回到合法区间。这意味着你不能在 DAA 之后、下一条指令之前去观察 AL,看起来可能不对,其实指令执行完就已经修正了。理解这点能省下很多"是不是指令用错了"的自我怀疑时间。判断一个字节是不是合法 BCD,简单办法是看每个 4 位组是否都小于等于 9;调试时我就是靠这个口算规则一眼扫出问题的。

2. 六条调整指令的分工与共同套路

2.1 为什么CPU不给你一条"十进制加法"指令

有人会问,既然十进制运算这么常见,为什么不直接在硬件里做一个 BCD 加法器,一条指令就搞定?这背后是成本和收益的权衡。BCD 加法器需要在每个 4 位组里判断是否大于 9、是否要补 6,还要处理跨组的进位传播,电路面积比纯二进制加法器大不少。而 BCD 运算在通用计算里的占比很低,绝大多数场景用二进制就够了,硬件厂商自然不愿意为小众需求付出芯片面积和延迟的代价。早期确实有处理器直接支持十进制运算,但那是个别设计,x86 家族走的是"二进制运算加软件调整"这条路,用几条单独的调整指令来补偿。

这种设计还有个好处是灵活性。调整指令是独立的指令,你可以控制什么时候调整、要不要调整,甚至可以只调整低 4 位。代价是编译器必须显式生成这些指令,代码体积和指令周期都会增加。所以后来的编译器基本放弃了 BCD,改用整数放大法或者定点小数来模拟十进制。理解了这层历史,你就明白这六条指令不是"过时的鸡肋",而是特定年代软硬件分工的一种妥协,学它们更多是为了读懂老代码和维护老系统。

2.2 加减乘除四类场景对应的六条指令

六条调整指令按运算类型可以分成三组,弄清楚分组关系后再记细节会轻松很多。

运算类型BCD形态调整指令配合的运算指令
加法压缩BCDDAAADD / ADC
减法压缩BCDDASSUB / SBB
加法非压缩BCDAAAADD / ADC
减法非压缩BCDAASSUB / SBB
乘法非压缩BCDAAMMUL
除法非压缩BCDAADDIV(AAD要先执行)

压成一张表之后规律就很明显:压缩 BCD 只涉及加减,用 DAA 和 DAS;非压缩 BCD 加减乘除全都要用,分别对应 AAA、AAS、AAM、AAD。为什么压缩 BCD 没有乘法除法调整指令?因为压缩 BCD 的乘除结果位数会膨胀,两位乘两位可能得到四位十进制,一个字节根本装不下,硬件没法用一条固定路径完成,所以设计上就没提供,只能拆成非压缩 BCD 逐位算或者转成二进制算完再转回来。这个设计取舍值得记住,遇到需要乘除的 BCD 场景,先把数据展开成非压缩形式是最省心的做法。

2.3 隐含操作数AL与标志位AF/CF的联动关系

六条调整指令有一个共同的套路:它们都不带显式操作数,默认操作 AL,而且都要读和写标志位。这一点跟常见的 ADD、MOV 很不一样,新手最容易在这里犯错,以为能写 DAA BX 或者 DAA AL。实际上 DAA 后面的操作数写出来汇编器会直接报错,因为它根本没有操作数字段。默认操作 AL 这个设定源于历史:早期处理器指令编码空间紧张,把最常用的累加器设为隐含操作数能省下不少位数。

真正需要重点盯住的是 AF 和 CF 这两个标志位。AF 是辅助进位标志,记录的是低 4 位向高 4 位的进位,也就是 bit3 往 bit4 的进位,它正是 BCD 调整要看的信号。CF 是进位标志,在压缩 BCD 里代表十进制进位,在非压缩 BCD 里功能相同但产生方式不同。DAA 和 DAS 会同时读写 AF 和 CF,AAA 和 AAS 也会设置这两个标志。麻烦的地方在于,你前一条 ADD 产生的 AF 和 CF 会被调整指令读取,而调整指令自己又会重新写一遍,所以如果中间夹了别的指令,很可能把标志位冲掉。我的习惯是 ADD 之后紧跟着写调整指令,中间绝不插入任何指令,包括看似无害的 MOV。

3. 逐条拆解:DAA/DAS/AAA/AAS/AAM/AAD怎么用

3.1 DAA与DAS:压缩BCD加减的六与六十

DAA 是十进制加法调整指令,执行时机是 ADD 或 ADC 之后。它的内部逻辑分两步走,第一步看低 4 位:如果 AL 的低 4 位大于 9,或者 AF 等于 1,就给 AL 加 6,并把 AF 置 1;否则把 AF 清 0。第二步看高 4 位:如果 AL 的高 4 位大于 9,或者 CF 等于 1,就给 AL 加 60H,并把 CF 置 1;否则把 CF 清 0。注意这两步是顺序执行的,第二步判断的是第一步已经改过的 AL。所有"加6"还是"加60H"的疑问,回到"二进制进位阈值是16、十进制是10、差值是6"这个原点就通了。

DAS 是十进制减法调整,逻辑对称:低 4 位大于 9 或 AF 等于 1 就减 6,高 4 位大于 9 或 CF 等于 1 就减 60H,同样顺序执行。减法为什么也是减 6 而不是加 6?因为借位方向反了,二进制在多借了 6 之后需要减回来。我用纸笔推过一遍 04H 减 08H 的情况:二进制算出来是 FCH,低 4 位 C 大于 9,减 6 得到 F6H;高 4 位 F 大于 9,减 60H 得到 96H。96H 加上一个 100 的借位,结果就是 -4,跟十进制 4 减 8 等于 -4 完全对上。这种纸面推导花不了几分钟,但能把规则从"记住"变成"理解"。

注意:DAA 和 DAS 只对 AL 操作,AX 的高位字节 AH 不受影响,做多字节压缩 BCD 运算时要自己把 CF 传给下一个字节。

3.2 AAA与AAS:非压缩BCD加减与进位传递

AAA 是 ASCII 加法调整,处理非压缩 BCD 加法。规则是:如果 AL 低 4 位大于 9 或者 AF 等于 1,就给 AL 加 6、给 AH 加 1、置位 AF 和 CF;否则清 AF 和 CF。最后无论哪种情况,都把 AL 的高 4 位清零,保证 AL 里只剩下一位十进制数字。这里 AH 加 1 值得单独说一句——这是 AAA 和 DAA 最大的区别。非压缩 BCD 一个字节存一位数字,十进制进位必须送到高位字节去,所以 AAA 顺手帮你把 AH 加了 1,DAA 就没有这个功能,因为压缩 BCD 的进位是字节之间的进位,得靠 CF 手动传。

AAS 是非压缩 BCD 减法调整,逻辑对称:低 4 位大于 9 或 AF 等于 1 时,AL 减 6,AH 减 1,置位 AF 和 CF;否则清标志,最后同样把 AL 高 4 位清零。减法的方向要特别注意,借位时 AH 是减 1 而不是加 1。我在写多字节非压缩 BCD 减法时一开始把 AH 写成了加 1,结果低位借位时高位反而变大,排查了半天才发现是把 AAS 的规则跟 AAA 记混了。这两条指令一个加 AH 一个减 AH,是必须分清楚的地方。

实操心得:AAA 和 AAS 都会直接改写 AH,如果你在循环里拿 AH 当临时变量用,务必先把它保存到别的寄存器或者压栈,否则循环两圈之后 AH 就被冲没了。

3.3 AAM与AAD:乘除调整里的隐藏除法

AAM 是 ASCII 乘法调整,默认形式是 AAM,等价于 AAM 0AH。它的动作是把 AL 除以 10,商放进 AH,余数放回 AL。比如 AL 等于 0DH,也就是 13,执行 AAM 之后 AH 变成 1,AL 变成 3,正好把 13 拆成十位和个位。这条指令看起来是"调整",骨子里其实是一次除法运算,所以它的执行周期在早期处理器上明显比别的调整指令长。AAM 最常见的用法不是做乘法,而是把 0 到 99 之间的数拆成两位十进制数字,再各加 30H 转成 ASCII 送去显示。

AAD 是除法调整,用法跟其他五条完全相反——它不是放在运算之后,而是放在 DIV 之前。执行 AAD 时,AL 被重新计算为 AH 乘以 10 再加 AL,AH 清零。这一步是把非压缩 BCD 形式的被除数转换成纯二进制数,然后再做 DIV,得到的商和余数再各自变回 BCD。我刚开始学的时候总是记混方向,后来用一句话锁死:AAD 是"进除法之前先把十进制数掰成二进制",AAM 是"乘法出来之后把二进制数掰成十进制"。两个字母顺序不同,执行位置和方向也正好相反。

隐藏细节:AAM 和 AAD 的立即数字节是可以改的,操作码分别是 D4 和 D5,后面跟的那个字节默认是 0A(十进制 10),改成 02 就变成了按二进制拆分或者按二进制合并。这个技巧在需要按其他进制拆分数字时很好用,能省掉一段除法代码。不过要注意,改成非 10 的基数之后,指令的语义就跟 BCD 无关了,只是借用了同一条机器码。

4. 上机实操:从单位加法器到多位BCD累加

4.1 环境与代码框架

为了把上面这些指令跑通,我用 16 位实模式环境做验证,工具链是常见的汇编器和调试器组合,代码段用标准的段定义结构。开头先定义数据段,放两组压缩 BCD 测试数据和一块输出缓冲区,代码段里用 PROC 和 ENDP 划分多个子过程,方便单独调试。之所以不直接在 64 位模式下测试,是因为 DAA、DAS、AAA、AAS、AAM、AAD 这几条指令在 64 位模式下根本不能编码,写进去会被判为非法指令,这一点后面单独展开。

DATA SEGMENT BCD_A DB 47H, 68H ; 压缩BCD,代表 6847 BCD_B DB 25H, 39H ; 压缩BCD,代表 3925 RESULT DB 2 DUP(0) ; 存放结果,留进位空间 MSG DB 'RESULT: $' DATA ENDS STACK_SEG SEGMENT STACK DW 64 DUP(0) STACK_SEG ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA, SS:STACK_SEG START: MOV AX, DATA MOV DS, AX ; ……后续运算代码 CODE ENDS END START

框架搭好之后先别急着写多位运算,从单字节开始验证。单字节压缩 BCD 加法是最小的可验证单元,跑通了再往上叠循环,调试成本会低很多。我见过不少人一上来就写四字节累加,结果出错之后根本不知道是哪一层的进位链断了,只能整段重写。

4.2 单字节压缩BCD加法的完整验证

单字节压缩 BCD 加法的核心就三条指令:把第一个数放进 AL,用 ADD 加上第二个数,然后紧跟一条 DAA。写出来是这样:

MOV AL, 47H ADD AL, 25H ; AL = 6CH DAA ; 调整后 AL = 72H,AF/CF 归零

手动推一遍这个过程很有必要。47H 加 25H,二进制逐位加得到 6CH;DAA 第一步看低 4 位,C 大于 9,给 AL 加 6,6CH 加 6 得到 72H,AF 置 1;第二步看高 4 位,7 不大于 9,而且 CF 等于 0,所以不再加 60H,CF 清 0。最终 AL 等于 72H,十进制 47 加 25 等于 72,完全正确。再看一个产生进位的例子:

MOV AL, 99H ADD AL, 01H ; AL = 9AH,AF = 1 DAA ; AL = 00H,CF = 1,表示十进制的 100

这个例子是我觉得最能说明问题的一个。99H 加 01H 得到 9AH,低 4 位 A 大于 9,加 6 变成 A0H,AF 置 1;接着高 4 位 A 大于 9,加 60H,A0H 加 60H 得到 00H 并产生进位,CF 置 1。结果是 AL 等于 00H、CF 等于 1,合起来读作 100。如果没有 DAA,直接拿 9AH 去显示,你会得到一堆看不懂的字符。这里 CF 不能丢,它代表百位,多位运算里必须把它继续往上传。

4.3 多字节BCD加法的进位链实现

多个字节的压缩 BCD 加法,关键是把每个字节的 CF 串成链。低字节算完产生的 CF 要参与高字节的加法,所以高字节不能用 ADD,得用 ADC。每一字节的流程固定为"取数、带进位加、调整、存回",用 LOOP 循环展开:

MOV SI, OFFSET BCD_A MOV DI, OFFSET BCD_B MOV BX, OFFSET RESULT MOV CX, 2 ; 两个字节,代表四位十进制 CLC ; 最低字节之前清进位 NEXT_BYTE: MOV AL, [SI] ADC AL, [DI] ; 带进位加 DAA ; 十进制调整 MOV [BX], AL INC SI INC DI INC BX LOOP NEXT_BYTE ; 循环结束后的 CF 就是最高位的十进制进位

这段代码有两个容易出错的地方。第一,循环之前必须显式 CLC,因为最低字节的加法不应该带上无关的进位,如果你前面刚做过别的运算,CF 里可能还残留着 1,会让结果莫名多一。第二,DAA 自己也会改写 CF,这正是我们要的——它把本次字节的十进制进位重新算出来,供下一轮 ADC 使用。整个循环的 CF 传递是自洽的,不需要额外保存标志位。

我用 BCD_A 等于 6847、BCD_B 等于 3925 这组数据验算过:低字节 47H 加 25H 加上初始 CF 等于 0,DAA 后得到 72H,CF 等于 0;高字节 68H 加 39H 加 0 得到 A1H,DAA 后低 4 位 1 不用加 6,高 4 位 A 大于 9 加 60H 得到 01H 并置 CF,所以高字节结果是 01H、CF 等于 1。合并起来就是 10772,跟十进制 6847 加 3925 等于 10772 完全一致。最终 CF 就是那个万位的 1,如果要显示出来,得在外面单独处理。

4.4 非压缩BCD输出到屏幕

运算结果要显示,就得把它变成可打印的 ASCII。非压缩 BCD 到 ASCII 的转换非常直接,每个字节低 4 位是数字 0 到 9,加上 30H 就变成 '0' 到 '9'。压缩 BCD 则要先把高低 4 位拆开,再分别加 30H。这里 AAM 是拆数字的利器:

MOV AL, 0DH ; 十进制的 13 AAM ; AH = 01H(十位),AL = 03H(个位) ADD AX, 3030H ; AH = '1',AL = '3' XCHG AH, AL ; 交换,让内存顺序变成 '1' 在前 MOV WORD PTR [BUF], AX ; 小端存储,BUF[0]='1',BUF[1]='3'

XCHG 这一步初看多余,其实很关键。x86 是小端存储,AX 写进内存时低字节 AL 会落在低地址。AAM 之后 AH 是十位、AL 是个位,如果直接存,内存里先出现个位字符,打印出来就是 "31" 而不是 "13"。交换 AH 和 AL 之后,AL 变成十位字符、AH 变成个位字符,写进内存的顺序就对了。这个坑我在第一次写数字显示时踩得很实在,屏幕上的数字全是反的,还以为是自己算错了运算部分。

5. 踩坑记录与排查速查表

5.1 典型现象与原因对照

调试 BCD 相关的代码,出问题的表现形式其实就那么几种,我把自己遇到的整理成一张对照表,出问题时先按这张表过一遍,八成能定位到原因。

现象可能原因排查动作
结果显示 11 一类的错误值加完没做 DAA/AAA 调整检查运算指令后面是否紧跟调整指令
结果比预期大 6调整指令执行了两次,重复加 6顺着代码看有没有重复调整
结果偏高或偏低 1循环开始前没有 CLC,带入了残留进位在多字节运算前加一条 CLC
高位字节莫名变化AAA/AAS 自动改了 AH检查 AH 是否另作他用
数字显示顺序颠倒小端存储把高位字符写在后面用 XCHG 交换序
汇编直接报错不通过在 64 位模式下写了这些指令改到 16 位或 32 位模式验证

这张表是我自己攒的,不代表官方文档,但每一条都来自实际调试现场。尤其是最后一条,很多人第一次在 64 位环境里写 DAA 会直接遇到"无效指令"的报错,怀疑是汇编器版本问题,其实是指令本身不被支持。把汇编参数切回 16 位模式,问题立刻消失。

5.2 我总结的几条实操心得

第一条心得是标志位要用调试器实时看。AF 和 CF 的变化在源码里看不出来,光靠推演很容易漏。我习惯在关键位置下断点,一条指令一条指令地看 AL、AH、AF、CF 四个值,只要这四个值对得上,BCD 运算基本就不会错。调试器里单独看 AL 是不够的,因为 CF 承载的高位信息经常被忽略。

第二条是非压缩和压缩不要混用调整指令。这两种形态的调整指令长得像,功能却不一样,混用的后果是得到一个看起来合法但没有意义的字节。我的做法是在代码注释里明确标出每个变量是"压缩 BCD"还是"非压缩 BCD",写的时候照着标签选指令,比记住六条规则要稳。

第三条跟前面的循环有关:在循环内部调用调整指令时,要注意循环寄存器本身会不会被调整指令影响。调整指令只动 AL、AH 和标志位,不动 CX、SI、DI,所以循环控制是安全的。真正会被影响的是 AX,如果你的高低字节都要用,记得先把其中一个移到别的寄存器里,不然 AAM 一执行,AH 里的东西就没了。

一个补充经验:写 BCD 代码时把每一处调整指令都当成"必须紧跟运算、中间不能插东西"来处理,养成习惯之后,标志位被冲掉这类问题会少很多。

6. 换个平台:Go汇编与现代替代方案

6.1 64位模式下这些指令为什么消失了

很多人学完 8086 汇编转头去写 64 位程序,会习惯性地想用 DAA,结果发现根本编译不过,甚至运行时报非法指令。这不是汇编器的问题,而是这几条指令在 64 位模式下确实不被支持。DAA、DAS、AAA、AAS 的单字节操作码分别是 27H、2FH、37H、3FH,AAM 和 AAD 是 D4 和 D5 带一个立即数,这一片编码在 64 位模式下被判定为无效,执行会触发异常。原因还是那个老问题:用的人太少,硬件厂商在精简指令集和编码空间时把它们砍掉了。

这个变化对写代码的影响是:现代平台上做十进制转换,只能改用算法而不是指令。常见做法有两类,一类是除法取余,反复除以 10 拿各位数字;另一类是查表加分组,把数字按两位一组处理,用预计算好的字符表直接映射。后一种效率高得多,因为它把逐位除法换成了少量的大数除法和内存查表。

6.2 用查表法和除十法做BCD转换

Go 语言的汇编沿用 Plan 9 风格,代码里写不了 DAA 这类指令,做整数转十进制字符串时,标准库用的就是前面说的查表加分组思路。它的做法是把数字按两位一组切开,比如一个 uint64 先除以 100 拆成高低两段,再对每一段继续处理,个位和十位通过一张长度 100 的字符表直接查出来。这里"两位一组"的思路跟 BCD 的非压缩打包非常接近——都是用十进制分组来回避昂贵的逐位除法,只是实现层面从硬件指令换成了软件算法。

如果你需要在 Go 里手工做非压缩 BCD 到数值的转换,可以照着 AAD 的逻辑写:把每一位数字乘以 10 累加。反过来做数值到 BCD 的拆分,就照着 AAM 的逻辑写:不断取模 10 和整除 10。下面这段代码就是把 AAM 的逻辑用高级语言重写的样子:

// 把 0-99 的数值拆成两位 ASCII,等价于 AAM 加上 3030H func toTwoDigits(n byte) (byte, byte) { hi := n / 10 lo := n % 10 return hi + '0', lo + '0' } // 把非压缩 BCD 转回数值,等价于 AAD func fromBCD(hi, lo byte) byte { return hi*10 + lo }

写到这里你会发现,汇编里那六条调整指令的精髓,其实不是机器码本身,而是"十进制分组"和"差 6 补偿"这两条思路。哪怕你现在完全用高级语言写业务代码,碰上金额计算这类必须精确到分钱的场景,理解 BCD 和定点十进制为什么比二进制浮点可靠,依然能帮你少踩很多坑——比如 0.1 加 0.2 在二进制浮点里永远不精确等于 0.3,而用十进制编码或整数分表示就完全没有这个烦恼。我在实际项目里处理金额时一直坚持用整数分存储,本质上就是当年学 BCD 时留下的习惯。

返回列表