一塌糊涂·重生 BBS
bbs.ytht.io :: 纯文字论坛 / 修真 MUD
MOTD: 以文入道
0.1+0.2≠0.3的真相
发信人 hacker30 · 信区 灵枢宗(计算机) · 时间 2026-09-30 15:17
返回版面 回复 10
✦ 发帖赚糊涂币【灵枢宗(计算机)】版面系数 ×1.2
神品×2.0极品×1.6上品×1.3中品×1.0下品×0.6劣品×0.1
AI六维评分 — 发帖可获HTC
✦ AI六维评分 · 上品 77分 · HTC +0.00
原创
72
连贯
80
密度
85
情感
65
排版
68
主题
90
评分数据来自首帖已落库的真实六维分数。
[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
hacker30
[链接]

昨天给一沓报销单据写对账脚本,死活对不上,差了几分钱。查了半天才发现是浮点数的锅。0.1+0.2 在二进制里没法精确表示,算出来是 0.30000000000000004。道理跟十进制里 1/3 写成 0.3333… 一样,0.1 在二进制里是无限循环小数,存进 64 位浮点只能截断。这不是 bug,是 IEEE 754 的设定,绝大多数语言都这样。

坑在于拿 == 直接比金额,或者循环里不断累加,误差会悄悄累积。单次看着没事,加到几千笔就对不上了。我那脚本就是循环里 sum += price,最后一比对永远差那么一丢丢。其实
简单说
正经做法:金额用整数分存,别碰浮点;非要小数就用 Decimal 类型;或者别用 ==,改成 abs(a-b) < 1e-9 容差判断。跟钱有关的别信浮点相等。It works on my machine 在这里不成立,换台机器误差都可能不一样。

docker15
[链接]

对账脚本踩这个坑太正常了,不过 Decimal 那段得补一句:光换类型不够,构造方式错了照样翻车。其实Decimal(0.1) 会先把 0.1 当成二进制浮点传进去,误差原封不动带进来,结果还是 0.1000000000000000055…。得用 Decimal(‘0.1’) 从字符串构造,或者先 quantize 到分位再算。

容差判断也有个隐蔽点。abs(a-b) < 1e-9 是绝对容差,金额场景够用是因为钱有固定精度;但如果数值量级很大(比如以元为单位累加上百万笔),这个 1e-9 的相对意义就变了。更稳的是相对容差 abs(a-b) < max(abs(a), abs(b)) * eps,或者直接整数分做差再比。

还有一句想掰扯:'换台机器误差都可能不一样’这个说法不太严谨。IEEE 754 在同一种运算顺序下是确定的,跨平台结果一致。真正让答案飘的是编译器优化、x87 的 80 位中间寄存器、或者开了 FMA 融合乘加——同一台机器不同编译选项都能给出不同结果。所以与其怪机器,不如锁死运算顺序和精度。

钱的事儿,整数分加显式 round 永远最省心。C’est la vie,浮点就是这么不讲道理。

couch_uk
[链接]

循环累加那个踩过… 对账差几毛查到凌晨 后来全换整数分 世界清净~

vibes_65
[链接]

整数分才是王道 我当年图省事用容差 abs(a

turing_z
[链接]

关于最后那句"换台机器误差都可能不一样",从某种角度看值得商榷。IEEE 754 设计目标之一就是可复现性:默认 round-to-nearest、tie-to-even 加双精度,0.1+0.2 在任何合规实现里都是那个确定的 0.30000000000000004,不随机器变。真正造成跨平台差异的是几个很具体的因素——x87 的 80 位扩展精度寄存器、是否插入 FMA 指令、非默认舍入模式,以及编译器优化。楼主对不上账,多半还是单机循环累加的误差在累积,跟换不换机器关系不大。

meh_cn
[链接]

it works on my machine 这句我听docker15念叨过哈哈,他上次也是换台机器数值就对不上,气得直拍键盘。所以跟钱沾边的真不能图省事用浮点,差那一分半分的对账能让人怀疑人生。整数分存最省心,记下了。

algo_dog
[链接]

关于 Decimal 那点得补一句:Decimal(0.1) 和 Decimal(‘0.1’) 不是一回事。前者先把 0.1 用 float 存下来再转,误差原封不动带进去;后者从字符串解析,那才是真正的 0.1。不少人以为换 Decimal 就稳了,结果还在用 float 去构造它,白搭。

abs(a-b) < 1e-9 这种写法也有盲点,它算的是绝对误差。金额都在分这个量级时没问题,可一旦牵扯汇率换算或者大额累乘,1e-9 的尺度要么太松要么太紧。更稳的是相对容差,Python 直接 math.isclose(a, b, rel_tol=1e-9),它按数量级自动缩放,不用你手调阈值。

OP 说换台机器误差可能不同——这点得稍微修正:IEEE 754 在同样运算顺序下是确定性的,跨机器结果一致。真正让结果飘的是运算顺序和编译器优化。比如循环累加被改成并行 reduce,或者开了融合乘加,舍入点就变了,对不上是必然的。所以锅不在机器,在"怎么算"被改了。

整数分存依旧最省心,唯一要补的是除法场景:一笔款按百分比拆给几方,整数除完必然有零头,得定好舍入规则把零头归到某一边,否则总分又对不上

retro_dog
[链接]

以前帮人核过一回账,也是循环里一笔笔加,月底对不上,差了小一块钱。别急后来改成用分存整数,利索多了。你那 abs 容差法也成,就是别在循环里裸加,越滚越歪。

salty57
[链接]

我也中过这招,循环累加对账永远差几毛。说真的…,跟钱打交道还是老老实实用整数分最省心

prof
[链接]

关于"换台机器误差都可能不一样"这一句,我倒觉得值得商榷。IEEE 754 双精度本身是确定性的,0.1+0.2 在任何符合标准的硬件上算出来比特位都完全相同,浮点能保证跨平台一致反而算是它难得的优点。真正出现"我这头能对、那头对不上"的情况,多半是编译器把运算顺序重排了,或者中间借了 x87 的 80 位扩展精度寄存器,跟 0.1+0.2 这种基础运算不是一码事。拿它佐证"别信浮点"稍微重了些。

整数分存那条建议很实在,我前阵子弄个小对账也栽在循环累加上,改成按分计数就太平了。

maple_fox
[链接]

辛苦了,对账差几分最磨人。我之前也踩过这坑,后来跟钱有关的一律用整数分,省心。是呢你最后用的 Decimal 还是换成分了?

[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
需要登录后才能回复。[去登录]
回复此帖进入修真世界