float无法精确表示0.1是因为IEEE 754双精度用二进制存储,而0.1在二进制中是无限循环小数,只能截断近似,导致如0.1+0.2≠0.3等误差。

为什么 float 类型天然无法精确表示 0.1 这类小数?
Python 的 float 基于 IEEE 754 双精度标准,用二进制存储——而像 0.1 这种十进制小数在二进制中是无限循环小数(类似十进制里的 1/3),只能截断近似。这不是 Python 的 bug,而是所有遵循该标准的语言共有的底层限制。
典型现象:0.1 + 0.2 == 0.3 返回 False;round(2.675, 2) 得到 2.67 而非预期的 2.68(因内部存储值略小于 2.675)。
decimal 模块:需要人工指定精度的“银行级”计算
当业务要求严格十进制精度(如金融、会计),decimal 是最直接的选择。它不依赖二进制浮点,而是用整数+小数位数的方式存储数值,但必须显式控制精度和舍入规则。
- 初始化时避免用
float字面量:写Decimal('0.1'),而不是Decimal(0.1)(后者会先走 float 转换,已失真) - 设置全局精度:用
getcontext().prec = 28控制运算结果的最大有效位数(注意:这是“最大位数”,不是小数位数) - 除法必须指定舍入策略:例如
Decimal('1') / Decimal('3')会报InvalidOperation,需写成Decimal('1').divide(Decimal('3'), context=getcontext())或提前设好getcontext().traps[DivisionByZero] = False
示例:Decimal('1.1') + Decimal('2.2') == Decimal('3.3') 返回 True;而 Decimal('1.1') + Decimal('2.2') 输出 Decimal('3.3'),无隐式转换污染。
立即学习“Python免费学习笔记(深入)”;
fractions 模块:适合有理数运算且不希望引入小数误差的场景
如果参与计算的数能明确表达为分数(比如比例、概率、物理公式中的常量),fractions.Fraction 可全程保持精确有理数运算,结果仍是分数形式,无任何小数舍入。
- 构造方式灵活:支持字符串(
Fraction('1/3'))、整数对(Fraction(1, 3))、甚至float(但会转成该 float 最简分数近似,如Fraction(0.1)→Fraction(3602879701896397, 36028797018963968),慎用) - 运算结果自动约分:
Fraction(2, 4) + Fraction(1, 3)得Fraction(5, 6) - 转为小数需显式调用
float()或limit_denominator()(后者可控制分母上限,逼近为较简分数)
适用边界:输入数据本身是整数比(如 3/4、16:9),或算法逻辑天然基于分数(如插值、概率树)。一旦涉及无理数(π、√2)或测量值(带误差的实测数据),Fraction 就不再适用。
什么时候该放弃“精确”,转而接受浮点并控制误差?
多数科学计算、图形渲染、机器学习训练并不需要绝对十进制精度,而是关注相对误差是否在可接受范围内。此时硬套 decimal 或 Fraction 反而拖慢速度、增加复杂度。
- 用
math.isclose(a, b, rel_tol=1e-9)替代==判断相等 - 避免累积误差:减少连续加减次数,优先用向量化操作(如 NumPy 的
np.sum(arr, dtype=np.float64)比 Python 原生sum()更稳) - 关键阈值比较时预留容差:比如
if x > 0.5 - 1e-10:而非if x > 0.5:
真正难处理的是混合场景:比如用户输入 '1.23'(应精确)和传感器读数 1.2299999999999999(本质是噪声数据)。这时得按数据来源分类处理,不能一刀切用某一种类型。


















