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

前阵子帮人查一个对账脚本,死活对不上,差个几分钱,单笔看完全正常。翻到核心才反应过来,金额全程用的 float。0.1 在二进制里压根不是精确的 0.1,是个无限循环的近似值,每次加减都悄悄舍入那么一丢丢。单笔误差小到小数点后十几位,肉眼根本看不见,可一旦循环累加几千上万笔,这些舍入误差会一路叠上去,今天差 0.01,月底能差出一块多。

有人图省事用 toFixed 截一下,或者转字符串比对糊弄过去,那都是把问题藏起来,不是解决。正确路子很直接:金额别用浮点,存整数分(或者更小的单位),要精度就上 Decimal 类型。整数加减没有舍入问题,对账才能严丝合缝。

Genau,踩过一次就记住了。其实现在只要看到代码里拿 float 处理钱,我第一反应就是把它揪出来换掉。

potato__de
[链接]

差一分都要命哈哈 float碰钱就是原罪 我也见一次想删一次

binaryist
[链接]

顺着楼主说的「上 Decimal」往下补一刀:Decimal 用错了照样翻车。根因不在于浮点本身,而在于很多人把精度当成开关——啪一下切过去就以为万事大吉了。

真正要补的是规则,几个容易踩的坑:

  1. 构造方式决定精度上限。还是拿 Python 举例:
    Decimal(0.1) # 在 float 阶段就丢过精度了,实际是 Decimal(‘0.1000000000000000055…’)
    Decimal(‘0.1’) # 这才是真·0.1
    所以如果你从接口或 CSV 里读到的是 float,再塞进 Decimal,源头已经脏了,后面怎么算都救不回来。正确做法是全程用字符串或整数进 Decimal。

  2. 舍入模式默认不是你以为的那个。Decimal 默认 ROUND_HALF_EVEN(银行家舍入),0.5 往偶数靠;但国内会计和绝大多数对账要的是 ROUND_HALF_UP(四舍五入)。不显式指定,月底那一分钱就可能从这儿冒出来:
    from decimal import Decimal, ROUND_HALF_UP
    Decimal(‘2.5’).quantize(Decimal(‘1’), rounding=ROUND_HALF_UP) # 得 3
    不写 rounding,默认给你 2。

  3. 整数分只救得了加减。一旦涉及分摊、折扣、利率这类除法,整数分就不够用了。100 分三个人分,33/33/34 还是 34/33/33,余数分配策略得单独定义,否则三笔一加照样对不上。所以真实系统常见组合拳:存储用整数最小单位 + 计算用 Decimal + 每处除法显式声明舍入和余数处理。

另外 toFixed 那个坑楼主点得准,不过 JS 里 (1.005).toFixed(2) 返回的是 “1.00” 而不是 “1.01”,因为它先按浮点近似再舍入。所以「截一下」不但不解决,还可能让结果更偏。

你们那个脚本最后是按笔逐差定位的,还是直接全量重算了?我挺好奇几千笔里到底是哪笔先开始漂的,有没有个能画出累积误差曲线的小工具。

nope54
[链接]

说真的,转字符串比对那招也敢叫解决?误差又没蒸发,不过是挪到看不见的地方蹲着。老老实实上整数分,不香吗。

yolo_jr
[链接]

Genau用地妙 float藏误差太阴了 单笔看不出 堆几千笔直接整出一块多 还是整数分踏实

bored_v
[链接]

差哪一块多钱够我加份毛肚了哈哈 对不上账真能逼疯人

random2003
[链接]

看到你甩了句Genau我就乐了 这词我天天挂嘴边 至于float那坑 踩过一次才长记性啊

bloom__dog
[链接]

那句"把问题藏起来,不是解决",读到这儿心里轻轻颤了一下。

古人讲差之毫厘,谬以千里,我从前总以为是在说什么了不得的大事,原来几行代码里早就悄悄应验了。0.1在二进制里永远凑不出一个圆满的数,像极了日子里那些怎么都抹不平的零头,你以为自己不计较,其实是每次都悄悄舍入了一点点。单看一笔,毫无破绽;可日子是一笔一笔叠起来的,等到月底对不上账…,才惊觉那些被忽略的"差不多",早已长成了一块多。
怎么说呢
你说的"整数分"三个字,我倒觉得不单是写代码的事。人这一生,最难的大概就是肯不肯把那些小数点的委屈,老老实实按"分"来算,而不是拿 toFixed 一截,蒙混过去还觉得自己体面。藏起来的裂缝,总有一天会在某个深夜漏出水来。

只是有点好奇,咱们对钱能这么较真,一分一厘都要严丝合缝,可换作感情、换作时间,反倒都肯用 float 凑合着过了。越重要的东西,人好像越懒得精确……

penguinist
[链接]

几分钱能滚成一块多,这账自己偷偷长腿跑了都没人发现,吓死个人

tesla_671
[链接]

帖子有个地方值得较较真:说整数分加减没舍入问题,这在纯加总场景里站得住,可钱一旦进乘除就漏了。
嗯
举个具体例子,一笔 100 块按 0.1% 费率抽成,或者三个人平摊一笔带零头的账单,这里都有除法。整数分也好、Decimal 也好,1 除以 3 在有限精度下都除不尽,落账那一刻必然按某个规则舍入。舍入选 round-half-up 还是 banker’s rounding(四舍六入五成双),月底对出来的总数能差出好几百。这不是数据类型能替你决定的,是业务规则。其实

Decimal 也不是银弹,它只解决"十进制预期"和"二进制存储"之间的错位,自身精度依旧有限,超长小数照样截断。所以严格讲,正确路子不是"换整数/Decimal"六个字能概括的,而是定单位 + 定每一步的舍入语义 + 在边界显式处理,乘除那段最容易被略过。

顺带一个数据印证楼主说的 toFixed 靠不住:JS 里 (1.005).toFixed(2) 返回的是 “1.00” 而不是 “1.01”,IEEE 754 的舍入方向跟直觉反着来。这种舍入规则你们一般写死在业务层,还是落库时统一处理?

noodle_uk
[链接]

前排留名。循环累加真能把人搞疯,我之前对账差几毛查了一下午,后来全换成整数分就再没出过幺蛾子。toFixed那个操做…糊弄鬼呢,月底该露馅还是得露馅

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