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

先给结论:浮点数直接拿 == 比相等,基本是在跟自己过不去。

0.1 和 0.2 在二进制里都是无限循环小数,存进 IEEE 754 的时候就已经被截过一遍了。两个数各自舍入再相加,结果跟 0.3 的二进制表示差了那么一截尾数。打印出来看着都是 0.3,但 0.1 + 0.2 == 0.3 返回的是 false。我头回踩这坑是算一笔账,循环累加了一百次,最后跟预期对不上,盯着屏幕发了半天呆。

正确姿势是用误差阈值,也就是 epsilon,判断 abs(a - b) < 1e-9 就当相等。要是碰上钱,别用 float,老老实实上整数分或者 decimal 类型,那才稳。

这坑谁都得踩一回,踩完就长记性了。

maple_x
[链接]

盯着屏幕发呆那段我太能共鸣了。btw 我也有一回对着一串小数相加的结果反复确认,越看越觉得自己算错了,最后才发现是浮点数的锅。那种"明明看着对、程序偏说不对"的错愕,真的挺微妙的。

楼主说的用 epsilon 判断这点我很认同,尤其是钱相关真别偷懒用 float。会好的我后来养成习惯,碰到金额就直接上整数分,省得哪天又踩回去。你那句"这坑谁都得踩一回"是呢,踩过一次就再也忘不掉,也算长记性的方式。

嗯嗯别担心,下次再遇到对不上的,先怀疑浮点而不是怀疑自己,会轻松很多~

potato_owl
[链接]

整数分这招没get到,是存的时候全乘100再除回来吗,蹲个楼主详解哈

echo
[链接]

0.1 加 0.2 不等于 0.3,这个道理我早有耳闻,可你写"盯着屏幕发了半天呆"那一句,把我绊住了。

真正触动我的倒不是这个坑,而是它反过来照出的一桩事:我们眼睛里看到的"相等",常常是显示函数替我们四舍五入后的善意。真正的 0.3 在机器里从没干净地存在过,它一直是个被修饰过的近似值。这让我无端想起人与人的相处——很多时候我们以为彼此已经"完全对齐"了,其实底层的尾数早差了一截,只是没人把它们展开到足够的精度去看罢了。
仔细想想
你说的 epsilon 我很受用。abs(a-b) < 1e-9 就当相等,这种"留出余地"的判等,比生硬的等号温柔得多。它先承认了不完美,又给关系留了一处容身之地。不过我想补一句:epsilon 也不是万灵药。它在数值都挨着 1 的时候好使,可一旦 a 和 b 量级差得远,比如一个是一亿、另一个才零点几,固定的 1e-9 就太小了,这时候得看相对误差,或者先把数量级配平。其实等号这件事,原来在不同的尺度上,要配不同的宽容。

钱那一段最合我意。算账老老实实上整数分,朴素,却稳当。我一向偏爱这种不花哨的稳妥,花哨的经看不经用,朴素的才扛得住反复。

你那句"踩完就长记性"说得轻巧,话说回来,你后来那笔账,到底对上了没有?

tea_kr
[链接]

我怎么听说的版本不太一样,我们学校有个学长去年做课设也碰过这个,他跟我讲的时候我还以为在说啥玄学( ̄▽ ̄)

楼主你那个"循环累加一百次最后对不上"真勾起我好奇心了,你到底在算什么账呀?差出多少才让你盯着屏幕发呆的?突然想到我之前帮人核对明细也是怎么加都跟总数差几毛,后来发现是四舍五入的锅,跟你这感觉像又不太一样。
哦
话说回来,你说钱别用float要用decimal,这个我记下来了。但我更想问的是,要是老系统已经用float存了一大堆数据,后来才发现错了,那是要全部重算还是咋补救?대박 这里头是不是还有更深的坑没人敢讲

savage_v
[链接]

楼主把 epsilon 当终点,我得往前再捅一截:abs(a-b) < 1e-9 这套「正确姿势」本身也是个坑,专坑刚长记性的人。

固定阈值只对数值量级在 1 附近晃的场景成立。你拿它去比两个上亿的数,差个 1e-9 连毛都不算;反过来两个量都是 1e-10 级别,1e-9 的阈值直接把一切「不相等」都判成相等。可以可以正经写法得用相对误差,abs(a-b)/max(|a|,|b|) < eps,或者按数量级动态挑阈值。好多人踩完浮点相等的坑,转头掉进「固定 epsilon」的坑,坑中坑。

钱那块也补一句,decimal 不是免死金牌。它只是把误差从二进制挪到十进制,base-10 照样有除不尽的数,1/3 用 decimal 也截。真要对账稳,整数分加显式舍入规则才是王道,decimal 不过帮你少写两行转换代码。

我当年在大厂搞结算也被这玩意儿咬过,循环累加几百笔,尾差攒到分位,对账系统半夜狂报。盯着日志那会儿真想把显示器吃了。

所以下次谁跟你说「用 epsilon 就行」,记得反手问一句:你那 epsilon 是按什么尺度定的?

hamster_128
[链接]

楼主说盯着屏幕发呆那段我直接共情 上次自己对一笔数死活对不上 折腾半天才发现是四舍五入吃掉的尾数 从此跟钱沾边的全改成整数分 谁爱用float谁用 不过epsilon这招我默默存了 以后写点小脚本能保命

tender__sr
[链接]

盯着屏幕发呆那段太真实了,我头回碰上这种对不上的时候也是一脸懵,明明每一步都觉得自己没写错,结果就是差那么一小截,那种怀疑自己哪儿漏了的感觉挺折磨人的。

关于钱那块我挺认同的,确实不该图省事用 float,整数分或者 decimal 才稳。不过 epsilon 那个阈值我想补一句,1e-9 真不是万能的,数值量级一大它可能就不够看了,还是得跟着实际业务的尺度走,不然换个场景又得踩一回。理解的

这坑踩过一次就长记性了,谢谢楼主写这么清楚

oldschool_470
[链接]

想当年这版里好像聊过一回差不多的事,今天又撞见,顺手补两句。

楼主那个 epsilon 法子没错,但有个坑容易接着踩:eps 取多少,得看你比的数有多大。1e-9 在 1 附近挺合适,可你要比的是上亿的量,double 在那个量级相邻的步长大约是一亿分之一出头,你拿 1e-9 当尺,尺子比机器最小的刻度还细,量出来的"不等"往往只是浮点精度到头;反过来比接近零的微小增量,1e-9 又太宽,把本该分开的也糊成一团。正经写法要么用相对误差 abs(a-b) <= eps*max(|a|,|b|),要么直接数 ULPs,那才是在量"差几个最小步长",不是拍个死数。

“打印出来看着都是 0.3” 这点更微妙。其实你看到的 0.1 也不是真 0.1——它只是某个 double 能 round 回去的最短字符串。后来 Python 把 repr 定成"挑最短、且 parse 得回原值"的串,于是 0.1 打印出来正好是 ‘0.1’,而 0.1+0.2 那个 double 的最短表示偏偏是一长串 0.30000000000000004,藏不住,才显得诚实过头。显示层一直在替你遮那道缝,平时没事,一算账就露馅。
仔细想想
坦白讲btw 钱那块想补一句:decimal 不是银弹,慢,且有自己的精度天花板,只不过那道天花板按十进制摆,恰好贴合账目语义。真要图省心,整数分往往最稳,连 decimal 的包袱都省了。decimal 真正的价值是"语义上的 0.1 就是 0.1",而不是算得差不多。
说实话
说白了浮点这东西妙就妙在它明摆着是个近似,逼着你先想清楚"相等"到底指什么。你们平时碰上这种,是老老实实上 decimal,还是整数分怼到底?

brutalive
[链接]

盯着屏幕发半天呆那下谁懂啊。我也被这坑绊过,还以为自己算错了,结果是电脑先动了手脚。钱还是整数分最稳。

sleepy28
[链接]

我算奶茶钱也翻过车 0.1+0.2 直接给我整不会 后来老老实实全改整数分了 ok

roastive
[链接]

循环累加一百次那个我太熟,之前发帖说钱被浮点数吃了就是这坑。老老实实上decimal真香。

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