ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python小数点处理7个实用技巧,彻底告别浮点误差

Python小数点处理7个实用技巧,彻底告别浮点误差 前段时间在HoRain云上部署一个量化交易数据的展示服务时被一组小数点数据折腾得不轻。明明数据库里存的是 0.1Python 读出来一算变成了 0.1000000000000000055511151231257827021181583404541015625前端展示直接多出一串奇怪尾巴。就是在那个项目里我系统梳理了一遍 Python 小数点处理的各种方案踩了不少坑也总结出了 7 个真正实用、能直接往生产代码里搬的技巧。这篇文章不打算讲那种“从入门到放弃”的教科书内容而是把这 7 个技巧按实际使用频率和场景拆开每个都配上可以直接跑的代码、参数选择和避坑指南。不管你是刚学 Python 的小白还是已经写了几年 Python 的老手只要你跟浮点数、金额、精度打过交道这篇文章都值得你花十几分钟看完。看完你会发现很多小数点问题根本不是你代码逻辑错了而是你压根没用对工具。1. 浮点数为什么“不靠谱”先弄清误差从哪来1.1 计算机里的小数本质是二进制近似很多人第一次遇到 Python 小数点问题时的反应是Python 是不是有 bug其实 Python 没 bug你的电脑也没坏问题出在计算机表示小数的方式上。计算机底层只能用 0 和 1 存储数据。整数还好说二进制可以精确表示但小数就没那么幸运了。比如十进制的 0.1换算成二进制是一个无限循环小数0.0001100110011001100110011001100110011001100110011...后面一直循环。但计算机的内存是有限的double 类型Python 的 float 就是 double只能用 52 位来存尾数部分多出来的位数只能丢弃或进位。于是 0.1 在内存里实际存的不是 0.1而是一个非常接近 0.1 的二进制近似值。这个近似值用十进制写出来就是 0.1000000000000000055511151231257827021181583404541015625。平时你直接 print(0.1) 看到的是 0.1因为 Python 在打印时会做舍入处理但一旦参与运算误差就会被放大。比如print(0.1 0.2) # 0.30000000000000004 print(0.1 * 3) # 0.30000000000000004 print(1.2 - 0.1) # 1.1看到没0.1 0.2 的结果不是 0.3而是 0.30000000000000004。这就是经典的浮点数精度问题任何一门编程语言都有Python 只是如实呈现罢了。1.2 误差什么时候会“咬”你一口说句实在话如果你的程序只是做个简单计算误差可能永远也暴露不出来。但下面这几类场景误差是必然会咬你一口的金额计算比如 19.9 元的商品打 8 折算出来是 15.92 还是 15.920000000000002直接决定你的订单系统会不会出账不平。数据比较if a b 这种判断0.1 0.2 0.3 的结果是 False如果你拿它做条件判断代码逻辑直接跑偏。循环累加在循环里反复做浮点累加误差会像滚雪球一样越滚越大。比如从 0 开始每次加 0.01循环 100 次你期望结果是 1.0实际可能是 0.9999999999999999。数据库读写从数据库读出十进制小数转成 float 算完再写回去前后可能就不是一个数了。搞清楚误差的来源后面 7 个技巧理解起来就顺了。说白了你要么在做计算时避开 float要么在做展示时把 float 伪装好要么在做比较时给误差留出缓冲带。2. 技巧一round() 的正确打开方式以及银行家舍入2.1 round() 的基本用法和你的直觉不一样print(round(2.675, 2)) # 2.67你以为会是 2.68 print(round(0.5)) # 0你以为会是 1 print(round(1.5)) # 2 print(round(2.5)) # 2你以为会是 3 print(round(3.5)) # 4这几个结果是不是很反直觉原因有两个层面。第一round(2.675, 2) 得到 2.67是因为 2.675 在内存里实际存储的值是 2.6749999999999998四舍五入取两位小数自然就成了 2.67。这不是 round 的 bug是 float 存储精度问题。第二round(0.5) 得到 0 而不是 1是因为 Python 的 round 采用的是“银行家舍入法”也叫“四舍六入五成双”。规则是当舍入位正好是 5 时看前一位如果是偶数就舍如果是奇数就入。0.5 的前一位是 0偶数所以舍1.5 的前一位是 1奇数所以入2.5 的前一位是 2偶数所以舍3.5 的前一位是 3奇数所以入。银行家舍入的意义在于减少统计偏差。如果你对一组成对出现的 0.5 进行传统四舍五入结果会系统性偏大而“五成双”能让舍入误差在统计上互相抵消。金融行业很多统计报表用这种方法是有统计学依据的。2.2 round() 的三个实战注意点用 round() 时我总结了三条经验第一round() 对负数位也有效。round(12345, -2) 可以按十位舍入得到 12300在做数据脱敏或粗略统计时有点用。第二round(x, n) 的返回值类型还是 float。如果你要的是精确的 Decimal 结果round 帮不了你它只是把 float 显示得好看一点而已。第三需要传统四舍五入时别用 round()。比如统计考试分数59.5 分按理应该变成 60 分round 按银行家舍入可能给你 59 分。这时候我习惯用 Decimal 配合 ROUND_HALF_UP后面技巧二会专门讲。from decimal import Decimal, getcontext, ROUND_HALF_UP # 传统四舍五入的正确写法 def round_half_up(num, digits0): quant Decimal(1. 0 * digits) return Decimal(num).quantize(quant, roundingROUND_HALF_UP) print(round_half_up(2.675, 2)) # 2.68 print(round_half_up(59.5, 0)) # 60这个函数是我在项目里最常用的自制工具之一比直接调 round 稳得多。3. 技巧二Decimal——金额计算的底线选择3.1 Decimal 到底是什么吃透它的原理如果你做的是电商、支付、财务、报表这类跟钱有关的系统Decimal 就是你的底线。它不是咬着牙硬写的 float 包装而是一种十进制定点数表示方案每一个数字都用十进制来存储和运算不经过二进制转换所以 0.1 就是真正的 0.1不存在二进制近似问题。我打个比方float 像是用一把毫米刻度的尺子量东西你量到 0.3 毫米时只能估读误差就这么来的。Decimal 像是用数字显示的数显卡尺你输入 0.1它内部就按十进制 0.1 来记忆运算时按十进制规则精确计算。多花了一点内存和运算时间换来的是精确可靠。from decimal import Decimal a Decimal(0.1) b Decimal(0.2) print(a b) # 0.3干净利落 print(Decimal(0.1)) # 0.1000000000000000055511151231257827021181583404541015625 print(Decimal(0.1)) # 0.1注意看上面这段代码的精髓Decimal(0.1) 和 Decimal(0.1) 结果完全不同。前者是把已经存成二进制近似值的 float 直接拿过来转换所以保留了所有误差尾巴后者是直接解析字符串得到的才是干净的 0.1。这个区别是我见过最多人踩的坑没有之一。3.2 Decimal 的精度控制和上下文设置Decimal 的默认精度是 28 位有效数字但你可以通过 getcontext() 调整from decimal import Decimal, getcontext getcontext().prec 50 print(Decimal(1) / Decimal(3)) # 0.33333333333333333333333333333333333333333333333333 getcontext().prec 6 print(Decimal(1) / Decimal(3)) # 0.333333这里要注意一个设计理念Decimal 的精度指的是“有效数字位数”不是“小数点后几位”。所以 Decimal(123.45) 和 Decimal(0.0012345) 在 prec6 的情况下前者能保留全部小数位后者直接被截断或舍入到 0.0012345 附近。需要固定小数位时要善用 quantizefrom decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN, ROUND_CEILING price Decimal(19.99) quantity 3 total price * quantity # total 59.97精确计算 tax Decimal(0.085) total_tax (total * tax).quantize(Decimal(0.01), roundingROUND_HALF_UP) print(total_tax) # 5.10quantize 的第一个参数决定舍入到哪个精度单位Decimal(0.01) 表示保留两位小数。rounding 参数可以选 ROUND_HALF_UP四舍五入、ROUND_DOWN直接截断、ROUND_CEILING向上取整、ROUND_FLOOR向下取整等。在订单系统里税额、折扣、分摊金额这些字段我全部用 Decimal quantize 来算账目才能对平。3.3 避免 Decimal 的隐藏坑别把 float 喂进去刚才提到 Decimal(0.1) 会有误差实际项目里最常见的翻车姿势是之前用 float 算好的结果传给 Decimal 再去除、求模误差瞬间传播。我见过有人在配置文件中写 Decimal(0.3)代码跑了一天后对不上账最后排查到是启动时读配置文件把值转成了 float。安全起见我给自己定了一条规矩Decimal 的输入只有三种——字符串、整数、Decimal 对象本身。凡是 float 参与过的结果绝不直接转 Decimal要么先用 str() 格式化干净要么从头到尾全程 Decimal。# 反例 amount_float 19.99 * 3 bad Decimal(amount_float) # 59.969999999999998863131622783839702606201171875 # 正例 amount_float 19.99 * 3 good Decimal(str(amount_float)) # 59.97当然更好的方式是根本不要让 float 出现在资金链路里但如果你接手了老项目str() 中转是成本最低的兜底方案。4. 技巧三格式化输出的三种武器展示层也必须守规矩4.1 f-string、format()、% 的精度参数详解有时候你不需要精确计算只需要让展示出来的数字好看。这时候 f-string、format() 和 % 格式化就是主力工具。三种写法都能指定小数点后位数value 123.456789 # f-stringPython 3.6 print(f{value:.2f}) # 123.46 print(f{value:.3f}) # 123.457 # format() print(format(value, .2f)) # 123.46 print({:.2f}.format(value)) # 123.46 # % 格式化老代码里常见 print(%.2f % value) # 123.46这里有个容易忽略的点格式化输出的舍入规则是“四舍五入”风格不是银行家舍入。也就是说2.675 用 f{2.675:.2f} 输出多少由于底层是 float2.675 实际存储值为 2.6749999999999998所以输出 2.67不是 2.68。这不是格式化的问题还是 float 精度问题。你要是真在乎那个 0.005 的差别就先把值转成 Decimal 再格式化from decimal import Decimal value Decimal(2.675) print(f{value:.2f}) # 2.68因为 Decimal 精确存储 2.6754.2 千分位、百分比和科学计数的展示技巧项目里做报表时数字展示不只是保留几位小数的问题还要处理千分位、百分比、科学计数等场景。number 1234567.891 # 千分位 print(f{number:,.2f}) # 1,234,567.89 print(format(number, ,)) # 1,234,567.891 # 百分比 ratio 0.2178 print(f{ratio:.1%}) # 21.8% # 科学计数法 big 123000000 print(f{big:.2e}) # 1.23e08 # 填充和宽度 print(f{number:20.2f}) # 右对齐占 20 个字符宽特别是 f{ratio:.1%} 这种写法直接帮你把 0.2178 变成 21.8%不用自己先乘 100 再加百分号省了很多事。在做数据大屏、运营报表的时候这些格式化技巧会让你的代码清爽很多。4.3 浮点转字符串的另一个坑str() 和 repr()还有一个新手常踩的坑str(0.1) 输出的是 0.1repr(0.1) 输出的也是 0.1看着一样但 repr(0.30000000000000004) 会把完整的精度尾巴打出来而 str() 会做一定的美化。在多端对接时这会造成诡异的问题。比如你在后端起一个服务把计算结果序列化成 JSON 传给前端float 在 JSON 里会完整保留精度尾巴前端展示出来就是一长串。我一般在返回前做一次统一格式化或者直接用 Decimal 转字符串再塞进 JSON避免前端拿着 59.96999999999999886 不知道该怎么办。import json # 直接序列化 float结果会带精度尾巴 data {total: 19.99 * 3} print(json.dumps(data)) # {total: 59.96999999999999886} # 统一转成两位小数字符串 data {total: f{19.99 * 3:.2f}} print(json.dumps(data)) # {total: 59.97}这个细节在我做数据接口时屡试不爽前端同事少了一堆吐槽。5. 技巧四整数运算思维——用 int 把小数问题消灭在源头5.1 金额用“分”存储比用“元”靠谱得多讲一个我强烈推荐的做法所有金额相关的字段在数据库里用整数分存储而不是用浮点元。这是很多支付系统、金融系统通用的做法不是拍脑袋想出来的。比如商品价格 19.99 元数据库存 1999分用户买 3 件总价就是 1999 * 3 5997分打 85 折5997 * 85 / 100 5097.45再按规则舍入到 5097。整个过程只有整数乘除不会出现 0.1 0.2 的尴尬。price_cents 1999 # 19.99 元 quantity 3 total_cents price_cents * quantity # 5997 discounted_cents int(total_cents * 85 / 100) # 5097折扣后 50.97 元为什么能这样因为整数在二进制里是精确表示的乘法不会引入精度误差除法的舍入逻辑你自己说了算。这样处理之后钱的问题一下子简单了。5.2 int() 截断和 math.trunc 的区别int() 不只是把小数转成整数它的规则是“向零取整”——正数直接砍掉小数部分负数也是直接砍掉小数部分。这个规则和 math.floor向下取整、math.ceil向上取整完全不同。import math print(int(3.7)) # 3向零截断 print(int(-3.7)) # -3向零截断 print(math.floor(3.7)) # 3向下取整 print(math.floor(-3.7)) # -4向下取整 print(math.ceil(3.2)) # 4向上取整 print(math.ceil(-3.2)) # -3向上取整 print(math.trunc(3.7)) # 3和 int 行为一致 print(math.trunc(-3.7)) # -3和 int 行为一致 int 和 math.trunc 效果相同都是向零截断。但如果你需要“总是往小取”或者“总是往大取”就要用 floor 和 ceil。举两个场景 - 库存扣减库存不能出现负数扣减数量向上取整更安全。 - 评分计算平均分按向下取整展示避免虚高。我在做数据分页和库存预警时经常在这几个函数之间切换搞混过一次之后就把它们的使用场景记在项目文档里了。6. 技巧五float 比较的正确姿势别再直接 6.1 绝对误差和相对误差的取舍回到开头那个问题0.1 0.2 0.3 是 False。如果你直接拿浮点结果做判断逻辑一定会出错。比较两个浮点数是否相等的正确姿势是不比较“相等”而是比较“足够接近”。标准做法是用 math.isclose()import math print(0.1 0.2 0.3) # False print(math.isclose(0.1 0.2, 0.3)) # True # 指定误差范围 print(math.isclose(0.1 0.2, 0.3, rel_tol1e-9, abs_tol1e-9)) # Truemath.isclose 有两个关键参数rel_tol 是相对容差默认 1e-9abs_tol 是绝对容差默认 0.0。用大白话说rel_tol 表示“如果两个数相差不到万分之一就算相等”abs_tol 表示“如果两个数直接差不到某个绝对值就算相等”。实际选择时有个参考数值很大时比如几百万的金额相对误差更合理数值接近 0 时比如 0.0000001 和 0.00000001绝对误差更合理不确定的话两个都设isclose 会取两者中更宽松的那个。6.2 带误差的区间比较有时候你不仅要判断相等还要判断一个数是否落在某个区间内。比如判断费率是否在 0.1% ~ 0.3% 之间rate 0.001999999999999999 lower, upper 0.001, 0.003 if lower rate upper: print(费率在合理区间内)这种写法如果边界是 0.001 这种二进制无法精确表示的数且你计算出来的 rate 是 0.0010000000000001那逻辑可能误判。稳妥一点的做法是把比较对象也统一成 Decimal或者把所有值先量化到相同精度再比较from decimal import Decimal, ROUND_HALF_UP rate Decimal(0.002) lower Decimal(0.001) upper Decimal(0.003) if lower rate upper: print(费率在合理区间内)我的经验是涉及比较的精度敏感场景直接全线 Decimal不要在 float 里绕来绕去只有性能敏感、精度要求不高的场景才考虑 math.isclose。7. 技巧六科学计算和高频场景下的精度与性能平衡7.1 什么时候别用 Decimal虽然我前面把 Decimal 夸了一通但它不是万能的。Decimal 的运算速度比 float 慢一个数量级因为它走的是软件模拟的十进制运算而 float 有 CPU 硬件指令直接支持。如果你在做科学计算、机器学习、图像处理、物理模拟这类场景数据量巨大但精度要求通常是十几位有效数字就够了用 Decimal 只会拖慢程序没必要。import time from decimal import Decimal # 粗略性能对比10 万次乘法 def float_mul(): a 1.1 for i in range(100000): a * 1.000001 return a def decimal_mul(): a Decimal(1.1) for i in range(100000): a * Decimal(1.000001) return a start time.perf_counter() float_mul() print(ffloat 耗时: {time.perf_counter() - start:.4f}s) start time.perf_counter() decimal_mul() print(fDecimal 耗时: {time.perf_counter() - start:.4f}s)在我的测试环境里Decimal 大概比 float 慢 5~10 倍。这个差距在单次计算里无感但在循环 10 万次的大数据处理里就很明显了。7.2 numpy 高精度方案的取舍用 numpy 做科学计算的人如果嫌弃 float 精度不够可以选择 float128在部分平台可用或者 numpy.longdouble但要注意float128 在 x86 平台上通常是 80 位扩展精度实际有效数字约 18 位比 Python 默认 float 的 17 位略高但依然不是精确十进制。如果你是做金融量化、需要数值稳定性我建议用 numpy 时尽量归一化数据避免直接对极端数量级做加减运算。比如先把价格序列做对数变换再计算收益率能显著减少浮点误差的累积。这个不是我瞎编的量化交易里常用这招。import numpy as np prices np.array([100.01, 100.02, 100.05, 100.03, 100.07]) log_returns np.diff(np.log(prices)) # 对数收益率误差累积更小 print(log_returns)对精度要求极其夸张的科研场景应该考虑 mpmath 等任意精度库甚至直接用符号计算库。但那是另一个战场了日常 Python 开发用到的不多。8. 技巧七Excel/CSV 数据读写的精度陷阱与应对8.1 pandas 读 CSV 时自动变成 float 的处理做数据分析的人天天跟 CSV 打交道最容易碰到的坑是明明 CSV 里写的是 123.45pandas 读进来变成 123.45000000000002。原因在于 pandas 默认会把数字列解析成 float64而 float64 对某些十进制值不够精确。解决思路有三种第一种读的时候指定 dtype 为字符串需要计算时再精确转换import pandas as pd df pd.read_csv(data.csv, dtype{price: str}) print(df[price].iloc[0]) # 123.45原样保留第二种读进来后用 Decimal 逐行处理适合数据量不大的场景from decimal import Decimal df pd.read_csv(data.csv) df[price_decimal] df[price].apply(lambda x: Decimal(str(x)))第三种如果只是展示或简单聚合直接用 pandas 自带的舍入方法比如 round(2) 之后再输出df[price] df[price].round(2)8.2 Excel 写入时的格式控制用 pandas 写 Excel 时如果直接写 float单元格里可能会出现一长串小数。我一般配合 ExcelWriter 和数字格式来写import pandas as pd from openpyxl import load_workbook output_path report.xlsx df pd.DataFrame({金额: [1256.345, 0.1 0.2, 9999.999]}) with pd.ExcelWriter(output_path, engineopenpyxl) as writer: df.to_excel(writer, indexFalse, sheet_name数据) worksheet writer.sheets[数据] for row in range(2, len(df) 2): worksheet.cell(rowrow, column1).number_format 0.00这样单元格显示两位小数底层存的还是原始浮点值。如果你希望写入 Excel 的值本身就精确可以先把 DataFrame 里的 float 转成字符串再写代价是 Excel 里它是文本而不是数字后续不方便计算。两种方案按需选择我一般选数字格式方案兼顾显示和计算。8.3 JSON 序列化 Decimal 的兼容方案现在很多项目前后端通信都走 JSON但 json.dumps 默认不认 Decimalfrom decimal import Decimal data {total: Decimal(59.97)} # json.dumps(data) # TypeError: Object of type Decimal is not JSON serializable解决方式有几种我最常用的是自定义编码器import json from decimal import Decimal class DecimalEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Decimal): return float(obj) return super().default(obj) data {total: Decimal(59.97)} print(json.dumps(data, clsDecimalEncoder)) # {total: 59.97}不过转 float 还是会丢精度尾巴。如果接口对精度有严格约定直接转字符串更稳class DecimalEncoderStr(json.JSONEncoder): def default(self, obj): if isinstance(obj, Decimal): return str(obj) return super().default(obj) print(json.dumps(data, clsDecimalEncoderStr)) # {total: 59.97}前端拿到的是字符串但至少不会出现 59.96999999999999886 这种鬼东西。前端配合做个 Number() 转换就行。9. 高频报错与翻车现场我的踩坑记录和速查表9.1 round 的舍入结果反直觉现象round(2.675, 2) 返回 2.67客户说这单金额算错了。原因2.675 的二进制存储值略小于 2.675round 按实际存储值舍入。解决金额计算全部改 Decimal舍入用 ROUND_HALF_UP。建议不要试图用 round 去做业务舍入它只适合普通展示场景。9.2 Decimal 和 float 混用报错现象Decimal(0.1) 0.2 直接抛出 TypeError。原因Python 不允许 Decimal 和 float 直接混算这是有意设计的保护。解决统一转 Decimal 或统一转 float推荐统一转 Decimalfrom decimal import Decimal result Decimal(0.1) Decimal(str(0.2))建议项目中写一个统一的 Decimal 工具模块所有资金计算入口都用它从源头杜绝混用。9.3 pandas 读入数据后精度漂移现象CSV 里是 88.88读进来变成 88.88000000000001。原因pandas 默认 float64 存储二进制近似导致。解决读入时指定 dtypestr或读入后 Decimal(str(x)) 做转换。建议分析数据如果涉及精确金额从源头就用字符串方式读入。9.4 JSON 序列化 Decimal 报错现象TypeError: Object of type Decimal is not JSON serializable。原因json 模块不认识 Decimal。解决自定义 JSONEncoder按需转 float 或 str。建议接口文档里提前约定小数传输格式避免前后端各自猜测。9.5 浮点累加误差导致统计不平现象100 个 0.01 累加结果等于 0.9999999999999999报表里合计不对。原因float 累加过程误差累积。解决用 Decimal 累加或者把小数换算成最小单位整数累加total_cents 0 for i in range(100): total_cents 1 # 单位是分 print(total_cents / 100) # 1.0建议涉及数量、金额累加优先用整数单位。10. 我在 HoRain 云项目里落地这一套的完整复盘10.1 一次从 float 到 Decimal 的迁移经历前面提到的量化数据展示服务最初上线时我用的是 float 直接计算收益率和累计净值。前期量小没啥感觉数据量大了之后在 HoRain 云上部署的服务开始出现净值对不上账的情况——数据库里某一行的净值跟前端展示的差了 0.00000001。排查到后来发现是收益率的浮点误差在几百次迭代后累积放大了。那次的迁移路径我按顺序做了几件事第一步所有涉及金额和净值计算的字段从数据库读取后立刻转 Decimal全程不再回 float。 第二步舍入规则统一为 ROUND_HALF_UP量化结果保留 6 位小数。 第三步对外接口的字段全部转为字符串输出前端展示统一格式化。 第四步对历史数据做一次清洗用 Decimal 重算所有累计值。迁移后账目就平了。这个项目也让我对 Python 的小数点处理有了系统认识后面总结出这套方案成了自己项目里的“标准作业流程”。10.2 工具函数库一行代码搞定 90% 的场景为了复用我把常用的小数点处理封装成了一个模块每次新项目直接拷贝或者 pip 装一下省了很多重复造轮子的时间。这里分享其中最核心的几个函数# precision_utils.py from decimal import Decimal, ROUND_HALF_UP, getcontext getcontext().prec 28 def dec(num): 安全转换支持 float/int/str/Decimal统一转 Decimal if isinstance(num, float): return Decimal(str(num)) return Decimal(num) def money(value, digits2): 金额格式化保留两位小数四舍五入 q Decimal(1. 0 * digits) return dec(value).quantize(q, roundingROUND_HALF_UP) def percent(value, digits2): 百分比展示 return f{dec(value) * 100:.{digits}f}% def safe_divide(a, b, digits8, default0): 安全除法除数为 0 时返回默认值 try: result dec(a) / dec(b) return result.quantize(Decimal(1. 0 * digits), roundingROUND_HALF_UP) except (ZeroDivisionError, InvalidOperation): return Decimal(default)这几个函数覆盖了我日常 90% 的小数点操作场景。新同事接手项目时只要记住“别直接用 float 算钱调 money() 和 safe_divide()”基本不会出大问题。10.3 最后再分享一个细节如果你也负责部署云服务建议在服务的健康检查接口里加一个精度自检逻辑——每次启动时自动验证 Decimal(0.1) Decimal(0.2) Decimal(0.3)、round_half_up(2.675, 2) 2.68 这类关键路径。一旦环境变了比如 Python 版本升级、依赖库变化导致行为异常立刻在日志里暴露而不是等到业务对不上账了才排查。这个习惯救过我一次当时是某个依赖库悄悄改了 Decimal 的默认精度直接影响了资金计算的舍入结果自检在第一时间就发出了告警。Python 小数点处理这件事说难不难说简单也不简单。难在于浮点原理比较反直觉简单在于你只要把规矩定好——计算用 Decimal展示用格式化金额用整数分比较用 isclose——大部分坑都能绕开。这篇文章里 7 个技巧没有一个是花架子都是我这些年写代码、查问题、改bug 时一点点磨出来的。希望对你有用。
返回列表