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

昨天帮一朋友查账,他程序里用float存金额,月底对账差了一分钱,俩人从下午查到半夜怀疑人生。我直接跟他说,你这锅得让二进制背。

说白了0.1和0.2在二进制里全是无限循环小数,计算机存不下只能硬切一刀,结果0.1+0.2算出来是0.30000000000000004这么个玩意儿,跟0.3比永远不相等。拿它做等值判断或者存钱,迟早出事。

我刚工作那会儿也踩过这坑,还跟同事拍桌子说编译器有bug,现在想想脸发烫哈哈。怎么说改法其实特简单,金额一律用整数分要么Decimal要么老老实实走字符串,比较别用==,整个误差阈值abs(a-b)<1e-9就稳了。太!
我去
这坑太经典了,谁还没被它教育过。你们还踩过啥离谱的浮点坑,说来听听。

mood89
[链接]

哈哈这坑谁没踩过 我之前写个统计脚本用float累加几千条小数 最后对不上 还以为数据源有bug 查了一下午 绝了

最离谱的是for(float f=0; f!=1.0; f+=0.1) 永远跳不出循环 f根本到不了1.0 看到同事这段代码我直接愣住

Decimal确实香 就是慢点 不过对账这点perf算啥哈

logic84
[链接]

最后那个 abs(a-b)<1e-9 的阈值,跟前面建议的整数分/Decimal 其实对不上。真用整数分了,比较就是整数相等,压根没误差,1e-9 是用 float 时的临时补丁罢了。而且金额量级一大,累积误差未必压在这条线底下,还是从源头别碰 float 踏实。

voidism
[链接]

你那个abs(a-b)<1e-9,对付一般浮点比较还凑合,拿来对付金额其实是治标。钱这档子事,最稳的还是整数分或者Decimal带rounding context。固定epsilon有个硬伤:数一大就露怯。账上跑出几百万的流水,俩数差个零点几还在阈值外头,可小数点后第九位那点误差早被量级吃没了。简单说真要比浮点,得用相对容差,或者像Python math.isclose那样拿rel_tol乘上max(abs(a),abs(b))做底线,才算靠得住。
其实
再补一个比0.1+0.2更阴的。double能精确表示的最大连续整数是2^53,约9e15,过了这道坎相邻整数都存不下。有人拿double当计数器,数往上走+1可能根本不加,还silently wrong,查都查不出来。

实务里栽得最狠的其实是舍入累积。一笔钱按比例摊到几十个科目,每个先round到分,末尾一加跟总额差一两分。这锅不在二进制,在取整时机——中间全程多留几位,最后一步才round。你朋友那"差一分"说不定也有这层,未必全是切一刀的祸。

NaN也提一句:它不等于自己,==永远是False,别拿当普通数使。

0.1+0.2这种,有的语言顺手round了显示成0.3,换一个又不round,最容易被当成"时灵时不灵的玄学bug",其实底层压根没等过。

feynman1
[链接]

你最后给的那条拿 abs(a-b) < 1e-9 当通解,我倒觉得在金额场景里最该打问号。1e-9 这个阈值本身没根儿——它既不是 double 的机器精度(1.0 附近 ulp 约 2.22e-16,差着六七个数量级),也不是业务上能解释的最小容差。拿它当金额相等判据,等于自己拍板定了个容差,对账的人未必认。

更要命的是误差会累积。一两笔 abs 差或许还在 1e-9 内,可成千上万笔累加,误差完全可能越过这条线,到时候"相等"判断又失灵。所以 1e-9 顶多算没招时的兜底,真要稳还是你前面说的整数分或 Decimal。不过"整数分"得加前提:分得是真整数类型,不是拿 float 存个看着像整数的数。double 精确表示整数上限是 2^53 ≈ 9.0e15,换算成分大概到 90 万亿 元才到边界——日常够用,可你要是以为"反正整数没事"、在浮点里加减完再取整,中间照样漂。

还有个小地方:你当年那句"编译器有 bug",锅其实不在编译器,是 IEEE 754 这个存储标准定的。0.1 在二进制里确是无限循环,谁存谁切,切完再算就偏,跟编译器聪不聪明没关系。

核心就一句:钱别碰浮点。整数分、Decimal、字符串都行,凡是用 == 或某个 epsilon 去"糊"浮点金额,都是在给将来的自己对账埋雷。你朋友能查到半夜也算运气好,至少发现问题了(笑)

stack29
[链接]

我也踩过这坑。刚工作时拿double做批量计价,月底差几块钱,对着屏幕怀疑编译器坏了,现在想起来也是脸发烫。

有一点想补:你最后说的那个abs(a-b)<1e-9,套在金额上其实不合适。钱是离散的,分就是最小单位,不该留什么误差容忍。用整数分或者Decimal,直接==比最干净。阈值那套是给一般浮点运算留的容错,硬塞到钱上反而坏事,哪天两个差一截的脏数据被它放过去了,对账更崩溃。

bref,金额就老老实实整数或者Decimal,别跟阈值较劲。你们还遇过啥离谱的,我蹲一个

lazy__us
[链接]

1e-9那个阈值我得补一句,科学计算里挺香,拿来比金额有点像拿创可贴补水管~钱是离散的啊,该整数分就整数分,真要比直接减完比零,何苦引入个阈值。吧阈值最怕尺度,你金额上到千万量级,1e-9相对误差照样能攒出实打实的差,回头对账又得查到半夜,绝了。

还有个真事比一分钱吓人多了,海湾战争那枚爱国者导弹,计时器里float累积误差攒了零点几秒,拦截时偏了,直接漏掉目标还搭上自己人。所以金额走Decimal或整数分我totalement赞成,但Decimal也不是神仙,1除3照样无限循环,换类型不等于万事大吉。

话说你们有没有遇过for循环里float看着一样死活不进判断的,那种才叫怀疑人生哈哈

stone72
[链接]

你那朋友后来咋收场的?差一分钱还算走运。我以前听人讲过一个更绝的,有个管库房的拿浮点记斤两,一天差一星半点没当回事,月底一盘点懵了,还当是闹了贼。

钱这物件,我还是觉得整数分最省心。Decimal也好,字符串也好,都比拿float硬扛稳当。你让他上误差阈值的法子也能凑合,可真要经手真金白银的活,我宁可打根上就不碰浮点,省得后头老悬着心。

这类坑,谁还没交过学费。

iris10
[链接]

盯着那串0.30000000000000004看,总觉得它像一封寄错了地址的信——你心里写着0.3,它偏多拖出一截尾巴。

你那句“脸发烫”把我逗笑了,也戳中了。谁没年轻过呢,对着机器拍过桌子,回头看只觉得那时的自己又可气又可爱。

这坑最动人处,是把“差不多”三个字摊开给人看:我们总默认世界能凑整,可底下一刀一刀切下去,总留着缝。那未必是bug,或许只是世界呼吸时留的空隙。你们还踩过啥,我搬板凳听着。

realist
[链接]

跟同事拍桌子说编译器有bug这段笑死,谁还没年轻过,那时候总觉得全世界就自己是对的。为一分钱从下午查到半夜也真是够上头,不过你朋友运气不错,撞上你这个明白人兜底。

我听人讲过更邪乎的,说早些年有个系统因为浮点累积误差,算着算着位置漂出去老远,差点出大乱子。所以你那句金额一律用整数分,真不是开玩笑。abs(a

nopeism
[链接]

哈哈你这朋友也是绝了,半夜查账查到怀疑人生,画面感太强。拍桌子说编译器有bug那段我直接笑出声,这事儿真不稀奇,我刚碰的时候也干过类似的事,还到处跟人信誓旦旦说这肯定是底层bug,现在回头看只想把当时的自己按住。

说到浮点坑,我印象最深的是有回做统计,循环里一直累加一堆小数,跑了几十万次之后总和跟手算差了快一块钱,当场以为数据脏了。后来才反应过来是累加误差在慢慢放大,跟单次 0.1+0.2 不是一回事,单次看着那点误差,架不住乘上循环次数。

你最后说的阈值我也补一句,abs(a-b)<1e-9 日常够用,但两个数量级差特别大的时候这 1e-9 其实不够看,正经还是按相对误差来更稳。不过金额这事就听你的,整数分最省心,谁用 float 谁后悔。

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