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

前几天改老代码又撞上这个坑。console 里敲一句 0.1 + 0.2,蹦出来的是 0.30000000000000004,不是 0.3。原因挺直白:十进制里 0.1 干干净净,换二进制就成了无限循环小数,IEEE 754 只给 53 个有效位,尾巴那截只能舍掉,误差从存储那一刻就埋下了。

最坑的不是单次差这么点,而是循环累加。把 0.1 加十次,你以为等于 1,实际差着个 epsilon。早年有兄弟拿 == 直接比金额,对账对到怀疑人生,账永远差几分钱对不平。

我的习惯:涉及钱一律用整数"分"存,展示层再除 100;要保精确小数位就上 decimal 定点数别用 float;两个浮点比大小也别硬等,abs(a-b) < 1e

prof_jr
[链接]

楼主最后那句 abs(a-b) < 1e 明显被截短了,不过顺着这个点想补一句:拿固定绝对值的 epsilon 去比两个浮点,数值跨度稍微一大其实就不可靠。严格来说

比如两边都是千万量级的金额,1e-6 这种容差相对数值几乎可以忽略,等于又退化回硬等;可要处理的是 1e-9 量级的微小量,1e-6 又宽松到没意义。我自己写小脚本对账时就踩过这个坑。更稳的写法是带相对误差,abs(a-b) <= eps * max(abs(a), abs(b)),eps 取接近机器精度再乘个安全系数就够了。

你们这种比较一般封个函数还是 inline 写。

vibes__513
[链接]

整数分存这招我一直在用,solid。那几分钱的差最阴了,对账对到怀疑人生太真实

phd
[链接]

我前阵子也踩过这个坑,顺手在控制台多试了两下,发现个有点意思的反例。0.1 连加十次落到了 0.9999999999999999,差那么一丢 epsilon;可改成 0.1 * 10,蹦出来的偏偏是干干净净的 1。同样是“十份 0.1”,乘和加走的舍入路径不一样,一个踩坑一个凑整。所以“加十次不等于一”严格说只在逐次累加这一种写法里成立,换成乘法就得另论了。

你末尾 abs(a-b) < 1e 看着是被截断了,我猜本意是 1e-9 一类。这里有个常被忽略的点:绝对容差只在数值都那么小的时候靠谱,一旦要比较的数量级上去了,同样 1e-9 的相对误差就压不住,正经做法多半还得带上相对容差。当然你“钱用整数分存”那条我是真心服气,比什么兜底都踏实。
其实
话说你那个对账对不平的兄弟,后来迁到定点数了还是改整数分了?

lazy
[链接]

0.1加十次那个最坑,看着像1实际差一截。我现在碰钱一律整数分,懒地跟浮点较劲哈哈

maple__dog
[链接]

那个拿 == 比金额、对账对到怀疑人生的兄弟,光是想想都替他心累。不过你整数分存的办法真稳,受教了。

dr_cn
[链接]

固定 epsilon 比大小这招,真实代码里比帖子写的要微妙一点。double 的相邻可表示值间距不是均匀的,越靠近 0 越密、越往大数走 gap 越大。abs(a-b) < 1e-9 这类固定阈值,碰上很大的数几乎永远是 false(两个数各自都"准",相减差个几万也正常),碰到接近 0 的小数又太宽松。更稳的是相对误差,即 abs(a-b) <= eps * max(abs(a), abs(b)),或者直接比 ULP(last place 的差值),后者在数值计算这块算更被认可的做法。

顺带一个容易撞的细节:你说把 0.1 加十次对不出 1,确实;但 0.1 * 10 在不少运行时里反而等于 1.0,因为乘法只做一次舍入,累加却是每一步都舍入一次再喂给下一步。运算顺序一变结果就漂,这也解释了为什么并行 reduce 和串行 for 循环求和有时会汇出不一样的数字——不是 bug,是 rounding 路径不同。
严格来说
整数分存钱这个习惯当然对,不过它解决的是"表示"问题,不解决"除法"问题。一笔 100 分要三七开,100/3 还是逃不开小数,得在业务层先定好舍入规则(round half to even 还是向上取整),否则尾差照样散到某一头。

你结尾那个 decimal 的建议最实在,凡是要精确小数位的场景,float 基本可以直接出局。

elder77
[链接]

循环累加那坑我早年真撞见过。有回帮人捋月度台账,几分几分地往上加,月底死活对不平,折腾半天才揪出是尾数在作怪……你那句 abs(a

tensor_47
[链接]

你那句被截了,abs(a-b) < 1e 后面没数字,应该是 1e-9 吧。

顺手提一个容易漏的:钱用整数"分"存这思路没问题,但 JS 里所有 Number 底层仍是双精度浮点,整数只在 2^53(约 9×10¹⁵)以内才精确。日常金额绰绰有余,可一旦对"分"再做乘除,比如汇率换算、算复利,中间值跨过这条线,误差就又回来。真要稳,BigInt 或 decimal.js 比手写除法靠谱。

epsilon 比较那块也补一句:固定阈值 1e-9 在数值极大或极小时会失准,得上相对误差。我前阵给手头一个小装置调固件,传感器采样累加,也是被这个坑了一回,后来全程整数计数才安稳。
其实
对账差几分这档子事,古人讲"差之毫厘,谬以千里",搁在钱上最贴切。

oldschool
[链接]

你那句 abs(a-b) < 1e 后面断在半道了,不过意思大家都懂。我年轻时候也在这栽过,不是钱的事,是有回给几百万条传感器读数做累加,浮点尾巴越滚越大,最后把报警阈值整串带偏了。后来就学乖:能整数就整数,真躲不开浮点就注意下累加顺序,从小到大加,漂移能收住一截。

ink_hk
[链接]

0.1 加十次差着的那个 epsilon,像极了日子。我们以为每一天都严丝合缝地接上了,回头一算,总和里总漏着一小截舍不干净的误差。钱能用"分"较真,有些东西的尾巴,谁也修不齐。

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