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

说真的,第一次看见 0.1 + 0.2 不等于 0.3 的时候,我还以为自己眼睛花了。Python 里敲出来是 0.30000000000000004,那一刻我真想替计算机找块豆腐撞死。道理不复杂,二进制浮点没法精确表达大多数十进制小数,可真落到自己代码里,那种"明明逻辑都对、结果就差那么一丢丢"的无力感,离谱得让人想笑。

最坑的不是算错,是它算得"差不多对"。好吧好吧我见过有人拿 float 累计订单金额,跑几万笔之后和财务对账,余额硬是差出几分钱。这 bug 单步调试根本看不出来,每笔只差个浮点尾巴,累积起来才露馅。财务可不管你什么 IEEE 754,人家只想知道那三分钱去哪了。emmm

后来学乖了,金额一律用整数存最小货币单位,也就是分,要算比例就上 Decimal 定点数。从表示层把这个坑填死,比事后写一堆四舍五入补丁强太多。还在用 float 算钱的,赶紧改,别等对账那天拍大腿。

mood89
[链接]

财务只关心那三分钱去哪了哈哈 这坑我当年也栽过

elder77
[链接]

我年轻的时候也踩过这坑。那会儿帮朋友弄个记账的小东西,哪懂什么定点数,直接拿 double 往上累加,跑了俩月才发现月底对不上账,查半天才搞明白二进制那点事。

不过说句实在的,整数存分肯定是对的,但 Decimal 也不是万能药——它慢,而且一样得先把 rounding 的规矩定清楚,不是挂上去就万事大吉。财务真正要的是确定性,每一项怎么舍怎么入,得白纸黑字写明白,不然换个人来维护照样出幺蛾子。坦白讲

你们现在这些语言封装得比我那会儿强太多了。

azure20
[链接]

第一次撞见 0.30000000000000004 那串尾巴的时候,我也愣了很久。后来才慢慢明白,浮点最磨人的地方不在它错了,而在它错得那么理直气壮,每一帧都近乎正确,连调试器都点头称是,可把成千上万个近乎摞起来,裂缝就自己长出来了。ach,你说的那种无力感,我太懂,像是盯着一面看起来平整的墙,走近才发现砖缝全是歪的。

想补一句:整数存分确实把表示层的坑填死了,但有些偏差其实不是浮点本身造成的,而是钱本就不能被整除这件事实。一笔钱按比率摊给三方,无论用 int 还是 Decimal,总会有分不下去的零头,这时候差出的那几分,根子往往在对账规则没说清余数归谁,而不在数据类型。Decimal 不是银弹,它只是把问题从看不见挪到了你得上手处理舍入的位置。

还有个小提醒,比较浮点数永远别用等号,留个 epsilon 容差才是正经做法。不过话又说回来,金额场合本就不该走到比较那一步,从一开始用整数就省了这桩心事。

最妙的或许是这个:机器用一套确定的规则,模拟出了一个不确定的世界。我觉得吧我们以为算得清,其实只是还没走到偏差显形的那一刻。

kernel_sr
[链接]

补一句你可能没点透的:那几分钱的缺口,主要不是加法累计出来的,是乘除法和中间取整一步步喂出来的。

double 做纯加法,量级相近的小数相加,相对误差也就 1e-15 上下。就算跑十万笔,光靠加法也攒不出几分钱。真正下刀的是每行先乘税率、折扣、手续费——这些带小数的比率一乘,立刻往结果里塞一段浮点尾巴,你再把这堆带尾巴的数往一块加,误差才被放大。所以"存整数分"只堵住了存储那头,中间要是还拿 float 算完比例再转回分,坑照样在。

正经做法是全程别离开整数分或 Decimal,取整只放在最末端——也就是展示和对账那一下 quantize,中间一律不四舍五入。再提醒一层:四舍五入和银行家舍入(round half to even)在 x.5 这一位上会系统性差 1,几万笔累积照样对丢几分钱,而且这跟浮点半点关系没有。Decimal 默认就是 ROUND_HALF_EVEN,不少人用了却不知道自己踩的是哪套规矩,财务那边若按四舍五入走,照样对不上。

Decimal 自己也不是无脑安全,除法不收敛(比如 1 除 3)得显式 quantize 定好位数。你们财务那边到底按四舍五入还是银行家舍入?这个不先对齐,Decimal 用得再标准也白搭。

regex_840
[链接]

用 float 累计订单金额这种事,好像谁都免不了踩一回。你那句替计算机找块豆腐撞死,太真实了。简单说

整数存分肯定是首选,最省心。Decimal 那条补一个坑:别写 Decimal(0.1)。从 float 转进去的那一刻尾巴就带着了,得用 Decimal(‘0.1’) 从字符串构造,否则以为切到定点数就安全,其实雷还在原地。

另外整数分也管不了所有情况。按比例分摊、算手续费这种要除法的,精度问题照样回来。得先定舍入规则,银行家舍入还是四舍五入,零头往哪边摊。整数解决的是累加不漂,解决不了怎么分。

说到底财务要的是可追溯。比换数据类型更实在的,是把每笔的舍入记清楚,哪步舍了多少一眼能看明白,这样对账的人也安心。

mehist
[链接]

那三分钱估计再IEEE 754里旅游呢哈哈。我以前也差过,改存分之后才算消停

sage_x
[链接]

我早些年也撞过这档子事。

那会儿不是算钱,是帮人做个小统计,把一堆带尾巴的小数比值往一块儿加,想着凑个整数好看些,结果每次都差那么一星半点,机器算出来的和手算的死活对不上,我对着屏幕愣了好一阵。后来才明白,这毛病是从计算机的根儿上带来的,二进制那套数,天生就跟咱们平时的十进位聊不到一块儿去,怨不得谁。

楼主那句"财务不管你 IEEE 754,只想知道三分钱去哪了",这话实在。技术上的道理讲一箩筐,落到眼前就是钱对不上,没得狡辩。整数存分、Decimal 定点,都是正经办法,我后来见着好些人都是这么绕开坑的。坦白讲

不过也别一听就慌,连夜把老代码全扒了重写。跑了多年的旧模块,里头 float 字段盘根错节的,一刀切有时候比留着打补丁还费劲。新写的立规矩,旧的慢慢挪,也挺好。

duckling90
[链接]

替计算机找豆腐那句太有画面感了。我之前帮人看个小账目也栽过,几分钱对不上,查半天才发现是 float 在作妖,换整数存分立马老实。

roast_z
[链接]

财务那句"三分钱去哪了"太真实了,我之前帮朋友对过一个小账目,两边死活差几毛,最后发现一个用float一个用Decimal,跟捉迷藏似的。Decimal虽好,除不尽的时候它也得按精度截断,照样可能差那么一丝丝,所以核心还是得先想清楚自己到底要几分精度哈哈

sage52
[链接]

以前还真不是这样,早年写点小工具,压根没现在这么多坑要防。我刚上手那会儿也被 0.1+0.2 吓过,对着屏幕愣了半天,还以为哪儿写错了。

整数存分这套我举双手赞成,最省心。不过 Decimal 也不是万能钥匙,它只是把精度问题挪到你看得见的地方。真碰到算利息、汇率那种带除法的,舍入规则还是得自己定。钱的事,最后靠的是人拍板,类型替不了你兜底。

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