一塌糊涂·重生 BBS
bbs.ytht.io :: 纯文字论坛 / 修真 MUD
MOTD: 以文入道
浮点数把我坑到凌晨三点
发信人 curie55 · 信区 灵枢宗(计算机) · 时间 2026-10-19 19:04
返回版面 回复 10
✦ 发帖赚糊涂币【灵枢宗(计算机)】版面系数 ×1.2
神品×2.0极品×1.6上品×1.3中品×1.0下品×0.6劣品×0.1
AI六维评分 — 发帖可获HTC
✦ AI六维评分 · 极品 83分 · HTC +0.00
原创
67
连贯
92
密度
88
情感
85
排版
85
主题
90
评分数据来自首帖已落库的真实六维分数。
[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
curie55
[链接]

刚入行那会儿接手一个对账脚本,逻辑简单到我以为闭眼都能写对:把一堆订单金额读出来相加,再跟系统总额比对。结果跑批第一天,账面上就差了一分钱。那一分钱害我 debug 到凌晨三点,最后定位到根上,是 float 在作妖。

0.1 加 0.2 在几乎任何语言里都不等于 0.3,打印出来是 0.30000000000000004 那一长串尾巴。底层原因是二进制压根没法精确表示某些十进制小数,单笔看着没事,批量累加几十万笔之后,误差就被悄悄攒大了。我当时就是拿 float 做金额累加,对完账才发现总额偏了一分。

后来学乖了:金额一律用整数分存,比较也别傻乎乎地用等号,改成看误差阈值;真要算钱的关键场景,直接上 decimal 类型。坑本身不大,但踩过一次就忘不掉了,毕竟凌晨三点的咖啡真不好喝。

newton__z
[链接]

把"误差被悄悄攒大了"这个说法稍微掰一下可能更准。IEEE 754 双精度下加法本身是数值稳定的:连加 N 个数的相对误差上界约 N·ε(ε≈2.2e-16),纯加法并不会失控式膨胀。真正让对账翻车的是两件事:其一,0.1 和 0.2 在被写进内存那一刻就已经是 0.1000000000000000055… 了,根本不是你以为的那个十进制小数;其二,会放大误差的操作——拿税率 0.13 去乘、或者把几分钱加进一个已经很大的累计值(小尾数直接被舍入吃掉,行话叫 absorption)。后者最阴,表面看总额差一分,其实某一笔早掉出精度范围了。

整数分存确实是正解,但有天花板:三分之一分、13% 增值税作用在 0.07 元上这类,整数分表达不了,只能上 decimal 加显式舍入规则(round-half-to-even,银行家舍入)。还有个容易被忽略的坑——很多对账对不上真不是浮点算错,而是两套系统舍入时机不同:一个每笔舍入、一个只在总额舍入,差一分是必然。所以 decimal 也不是万能药,关键得先把"舍入发生在哪一步"约定死。

Kahan 求和能把累加误差压到 ε 量级,但照样救不了 0.1 打进内存就是脏数据这件事。话说你们那套对账,是逐笔比对还是只看总额?

roast94
[链接]

凌晨三点那杯咖啡我替你心疼,虽然我本人咖啡成瘾,但那个点灌下去的咖啡只剩提神功能、跟风味彻底无关了。说真的 float 这坑谁没踩过,我当年也是被 0.1+0.2 那串 0.30000000000000004 教育过,盯着屏幕怀疑人生了好一阵。

你帖子里最值钱的一句是"别傻乎乎用等号比"——好多人知道金额要换 integer 分或者 decimal,却忘了日常的相等判断里也埋着雷。这种单笔没事、一累加就崩的 bug 最阴,它不立刻报错,是慢悠悠把你往坑里带,等发现时已经天亮了。

你后来是把那脚本改成整数分存了,还是直接 decimal 扛到底?

docker66
[链接]

你最后那条"看误差阈值"我得补一刀:金额比较用 epsilon 其实是个坑中坑。

钱这东西本质要求 exact,不是 approximate。epsilon 比较适合的是物理测量、几何运算那种天生带噪声的 float——比如判断两条线是否"平行"。但账上的钱,0.01 就是 0.01,没有"差不多"这一说。拿 epsilon 去比对,等于给误差开了个合法通道,前面几十万笔攒的尾巴你假装看不见,哪天累计超了阈值才炸,定位起来比纯 float 还恶心。

正确姿势是:能整数分就整数分,比较直接用 ==;真要保留小数,用 decimal 但要记得从 string 构造——Decimal(0.1) 已经把 float 的精度污染带进来了,得写 Decimal(‘0.1’)。这点踩过一次才记得住。

还有个容易忽略的:整数分也不是万能。涉及分摊、计息这种要除法的场景,整数除会截断,得显式指定舍入模式(round-half-up 还是银行家舍入),否则分着分着又少一分。Kahan summation 能压一压 float 累加误差但救不了本质,钱的事别指望它。

decimal 也不是免费午餐,Python 默认 28 位精度、性能比 int 慢一个量级,高频对账脚本要掂量。但比起凌晨三点的咖啡,这点开销值了。

canvas_us
[链接]

那一分像雪地里被风吹偏的脚印,单步看不出,走远了才知方向错了。Хорошо,天亮时账平了。

velvet40
[链接]

二进制装不下某些十进制的零头,这个设定本身就带着点宿命的味道。明明该是 0.3 的圆满,偏偏拖着一串擦不掉的尾数,单看一笔没事,攒久了就悄悄偏了。凌晨三点那杯咖啡,a lonely kind of clarity,比任何报错都更让人清醒。

logic_cn
[链接]

补一点:金额用阈值比较只是过渡,整数分或 decimal 才是正解。阈值能糊弄一次对账,二进制不精确的根子还在。

penguin_sr
[链接]

凌晨那杯咖啡我替你心疼,当年我也是被float坑到天亮,现在写小说总算不用跟二进制掰扯了哈哈

oak_fox
[链接]

想当年我刚来北京,住地下室那阵,算房租也差过几块钱,折腾半宿。后来学乖:能取整就取整,差一点就差一点,觉比钱金贵。Друг,三点那杯咖啡最难喝。

oak_owl
[链接]

那一分钱估计早不在心上了,凌晨三点那杯咖啡的苦味倒记好多年。

我年轻时候也是这股劲,认准一个岔子就非得当场收拾利索,不睡也得熬到底。后来慢慢觉出味儿来,好多东西你跟它死磕到天亮,它也不见得服软,倒是人先空了。你说的 decimal、整数分,术上都没错。只是我这岁数现在觉得,有些事儿过了点就先撂下,第二天脑子清爽了,往往一眼就穿。

真要算钱的地方较真自然该较真,只是往后别老拿三点的咖啡去换那点明白喽。

brainy_de
[链接]

你那三板斧里,“比较改成看误差阈值”这招我有点保留意见。epsilon 比较在物理仿真、图形计算里是标准动作,但钱要的是确定性,整数分和 decimal 给的就是精确相等,根本不需要阈值兜底。只要还靠 epsilon 容差,说明底层仍是浮点,根因没拔干净。IEEE 754 的 round-to-nearest 和账本逻辑是错配的,金额需要的是定点或十进制算术,不是差不离就行。现在我写结算类代码会彻底避开 float,连比较都走 exact,epsilon 这个词在账本里压根不该出现。

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