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

前两天又被浮点数摆了一道,忍不住上来水一帖吐个槽。我对一批订单金额做累加对账…,前端清清楚楚写着19.9元,后端算出来却偏要差那么一分钱。盯着代码半天才反应过来:0.1和0.2在二进制里都是无限循环小数,塞进double那一刻精度就丢了,所以0.1+0.2得到的从不是0.3,而是那个著名的0.30000000000000004。

嗯从某种角度看这事儿特别反直觉。我们写代码时总默认小数就该老老实实待在那儿,可IEEE 754底下它只是个近似存储,直接拿==去比两个浮点数相等几乎注定失败。正经做法是比较两者差的绝对值是否小于一个极小的 epsilon 阈值,或者涉及钱的地方干脆别碰浮点,老老实实用整数分或者 decimal 类型。

我后来把金额全改成按分存整数,对账再没出过幺蛾子。这种坑教科书往往一句话带过,真踩进去才知道它多阴。你们有没有被哪个看似人畜无害的小数坑过?

gauss_2004
[链接]

à propos,epsilon那段我想补一句:固定一个绝对阈值其实不够严。double的有效数字只有约十六位十进制,你拿同一个1e-9去比,比一万块和比一亿块,放过的相对误差差出四个数量级。工程上更稳的是按相对误差,或者按ULP来定阈值。嗯

不过钱这事儿你最后用整数分是对的,epsilon说到底只是浮点比较的 compromis,不该是算账的首选。我见过有人用固定epsilon对账,大额照样对不上,调了一下午阈值才反应过来。这种坑真得自己踩进去才知道多阴。

logic90
[链接]

你提的 epsilon 兜底其实还有个盲区:固定阈值只在两个数量级接近时才稳,遇到相差悬殊的数值,abs(diff) < 1e-9 既可能放过真实误差,也可能在小数量级上冤枉好代码。金额走整数分这条路选得对,但 float 比较没有一劳永逸的阈值,量级差太大时得上相对误差或者 ULP 那套才靠谱。

azure93
[链接]

那串 0.30000000000000004 看得我愣了一瞬,它多像一句没说完的话,后面拖着一长串迟迟不肯落地的零。

你写那种反直觉的错愕,我太能体会了。人总默认小数该老老实实待在原地,可底层的存储压根只是个近似。差的那一分钱,像缝隙里漏进来的风,看不见,却真切地凉。想起有回在超市,小票和手机支付对不上那一分,我头一遍还疑心是自己眼花了,后来才咂摸出味来,精确到分这件事,原来也是一层温柔的错觉。
话说回来
这种坑最磨人,就磨在它藏得太体面。表面风平浪静,底下早已悄悄偏了航。你后来按分存整数,等于把那点摇晃提前钉死,干脆得很。

tensor_47
[链接]

0.30000000000000004 这个数我第一次撞上也是一脸问号。

按分存整数确实是根治办法。不过想补一刀:decimal 也不是免死金牌。加减乘没问题,可一旦做除法,比如按比例分摊一笔钱,照样会冒出无限小数,最后还得显式 round 并定好舍入规则。银行那套是先汇总再分摊,零头丢给最后一个人,总分账总能平。

epsilon 那条也得看场合:绝对值小于阈值的写法只在两边量级接近时靠谱。金额都在几十到几百还好,真拿去跟极大或极小数比,绝对 epsilon 就失灵了,得上相对误差,或者比两者差了几个 ULPs。

lazy__us
[链接]

笑死 这个0.30000000000000004我第一次见的时候也是懵了半天。前阵子帮朋友弄个小程序算餐厅AA账,俩人各付19.9加19.9,结果程序吐出来39.799999999999997,当场裂开。我还以为是自己代码写炸了,debug到半夜才发现是double在背后搞鬼。对了离谱

后来也学乖了,碰钱一律按分存整数,省心太多了。不过说真的这种坑最烦的就是平时藏得深,你以为一切正常,对账那下突然给你来一下,绝了。
绝了
你们说的epsilon比较法我倒觉得治标不治本,钱的事还是整数分最稳。楼主有没有遇到过更离谱的,比如直接给你整出个NaN或者Infinity的,那才叫真的崩溃,loco哈哈

angel2002
[链接]

抱抱楼主,这种"明明该是0.3却偏差那么一丝"的别扭劲儿真的让人抓狂。不过你后面干脆改成按分存整数就特别利落,一步到位再也不用跟它斗智斗勇啦。小时候学算术总觉得小数就该乖乖待着,哪晓得底下门道这么多,不亲自摔一跤真不长记性呀。

sage_x
[链接]

这坑我年轻时也栽过。那时候刚学着摆弄电脑,老老实实写了个累加,屏幕上蹦出 0.30000000000000004 的时候,我还以为是显示器抽风了。盯着半天才反应过来,double 天生就是个「差不多先生」,你跟它要精确,它只会摊手耸肩。别急
坦白讲
后来碰到钱的事我学乖了,一律按分存整数,账目清清爽爽。epsilon 那套法子也不是没试过,但总觉得像给漏水的管子贴胶布——阈值设小了还漏,设大了又怕把真不一样的也给放过去,心累。所以你说的按分存整数确实是治本的路子。

你们对账一般留几位小数?我那会儿图省事没设格式,结果小数点后飘出来的位数能把人看晕。

spicyive
[链接]

笑死,0.30000000000000004这个老六我太熟了。你这段写得真通透,尤其那句“教科书一句话带过,真踩进去才知道多阴”,绝了,说得太准。行吧

不过我跟你有点不一样,我头回踩这坑不是在钱上,是做统计的时候,一堆浮点累加尾巴飘得离谱,我还当自己算法写错了,怀疑人生了半天,后来才反应过来是精度在背后偷偷搞鬼。

说真的,浮点最阴的不是它算错,是它大部分时候“看着对”,偏偏关键时刻给你掉链子。你那招按分存整数确实是王道,碰钱的事儿没必要跟浮点讲道理。你们有没有遇过更离谱的,比如同一段代码换个环境结果就不一样了?

cardio_z
[链接]

按分存整数这招稳…,涉及钱直接上整数就完事了!我之前看人搞对账也被这小数点坑得够呛。

git_cn
[链接]

你这个坑踩得典型,但按分存整数之后还有个尾巴容易被忽略:只要业务里出现乘除,精度问题就会从"表示"转移到"舍入规则"上。

金额按分存好了,可一旦要算折扣、税费、跨币种换算,还是得做除法,这时候"怎么舍入"成了新的对账雷区。两个系统一个四舍五入、一个用银行家舍入(round half to even),结果照样对不上。我见过最隐蔽的一种:A系统先乘费率再取整,B系统先取整再乘费率,差出好几分,查半天才定位。所以存整数只解决了表示问题,舍入约定得前后端写进同一份文档钉死。

epsilon 比较也有坑。固定绝对值 epsilon 只在数值量级接近时好使。要比的两个数一个 1e9、一个 1e-3,绝对误差阈值直接失灵。正经做法是看相对误差,或者按 ULP(units in last place,最后一位单位)比较——两者浮点表示差不超过几个 ulp 就算相等。不过金额既然走整数分了,这块一般碰不到。

还有个常见误解:decimal 不是无限精度。Python 的 Decimal 默认 28 位有效数字,SQL 里 decimal(p,s) 的 p 也有限,超了照样丢。它只是把"二进制表示不了十进制小数"这个坑挪走了,没消灭精度问题本身。

所以你们现在对账那套,运算环节里有没有除法?要是纯加减就稳了,一旦牵扯比例,舍入顺序比类型选择还更要命。

dear_ism
[链接]

前端写着19.9后端却差一分,这种看着一模一样却对不上的劲儿最磨人。我之前也傻乎乎拿 == 比过价格,对着屏幕发呆半天,后来干脆全按分存整数,世界一下子清净了。所以现在遇到纠结浮点精度的,我第一反应就是:跟钱打交道的活儿,能不碰浮点就别碰(笑)~

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