Python math.isclose 怎么选容差:接近零时,相对误差为什么不够用

10-01 4阅读

先把“足够接近”写成需求

校验传感器读数、核对数值算法结果时,两次计算不一定产生完全相同的浮点数。直接比较可能过严,但看到失败就随手加一个小数阈值,也可能掩盖真实偏差。使用 math.isclose 之前,应先回答允许的误差是什么:按读数的一定比例变化,还是在任何量级都允许一个固定的差值。

近似比较只返回一个判断,不会修改输入,也不会恢复计算中已经丢失的精度。测试变成通过,代表选择的准则接受这次差异,并不证明原来的算法正确。若需求要求数量严格相等,或偏差来自单位换算错误,替换比较函数没有解决问题。

两个阈值如何共同起作用

对于有限数值,判定会先计算两数之差的绝对值,再取两种容差阈值中较大的一个。相对阈值是 rel_tol 乘以两数绝对值中的较大者;绝对阈值就是 abs_tol。差值不超过这个最终阈值才通过。这里采用较大值,因此任一门槛足够宽就可以接受差异,无须同时满足两个门槛。

相对容差没有单位,绝对容差则必须与读数使用同一单位。默认相对容差为十亿分之一,默认绝对容差为零。把非零的小数与零比较时,相对阈值也跟着该小数一起缩小;只要相对容差小于一,再小的非零数也不会仅靠相对容差通过。

下面的完整程序使用标准库,保存后用 Python 三运行。示例把相对容差设为百万分之一,绝对容差设为十亿分之一,数值只是用于观察分支的教学选择。实际项目应根据测量分辨率、误差预算或算法指标重新确定,不要直接复制成全局常量。

Python math.isclose 怎么选容差:接近零时,相对误差为什么不够用

AI概念配图,非真实界面

import math

assert not math.isclose(1e-12, 0.0)
assert not math.isclose(1e-12, 0.0, rel_tol=0.01)
assert math.isclose(1e-12, 0.0, abs_tol=1e-9)
assert not math.isclose(2e-9, 0.0, abs_tol=1e-9)
print('near zero: relative=False, absolute=True')

assert math.isclose(1_000_000.0, 1_000_000.5, rel_tol=1e-6)
assert not math.isclose(1.0, 1.0005, rel_tol=1e-6)
assert not math.isclose(1_000_000.0, 1_000_000.5,
                        rel_tol=0.0, abs_tol=0.1)
assert math.isclose(0.0, 0.125, rel_tol=0.0, abs_tol=0.125)
assert not math.isclose(0.0, 0.25, rel_tol=0.0, abs_tol=0.125)
print('scale and exact boundary checks passed')

assert not math.isclose(math.nan, math.nan)
assert not math.isclose(math.nan, 0.0, abs_tol=1.0)
assert math.isclose(math.inf, math.inf)
assert math.isclose(-math.inf, -math.inf)
assert not math.isclose(math.inf, -math.inf)
assert not math.isclose(math.inf, 1e300, rel_tol=0.1)
print('special values: NaN=False, same infinity=True')

def reading_matches(actual, expected):
    if not (math.isfinite(actual) and math.isfinite(expected)):
        raise ValueError('readings must be finite')
    return math.isclose(actual, expected, rel_tol=1e-6, abs_tol=1e-9)

assert reading_matches(1e-12, 0.0)
assert reading_matches(1_000_000.5, 1_000_000.0)
for actual, expected in [(math.nan, 0.0), (math.inf, math.inf),
                         (0.0, -math.inf)]:
    try:
        reading_matches(actual, expected)
    except ValueError:
        pass
    else:
        raise AssertionError('non-finite reading accepted')

a, b, c = 0.0, 0.75, 1.5
assert math.isclose(a, b, rel_tol=0.0, abs_tol=1.0)
assert math.isclose(b, c, rel_tol=0.0, abs_tol=1.0)
assert not math.isclose(a, c, rel_tol=0.0, abs_tol=1.0)
print('finite reading policy and non-transitivity checks passed')

沿着输出检查比较规则

第一组断言说明,小到万亿分之一的数也不自动等于零。加入明确的绝对容差后才接受它,而超过绝对门槛的样本仍被拒绝。第二组展示同一个相对比例如何随数值量级变化;如果需求只允许固定偏差,就把相对容差显式设为零,避免大数额外获得宽松门槛。

边界测试使用八分之一这样的二进制可精确表示数,专门确认“不超过”包含边界本身。业务里若使用十进制阈值,阈值和输入也可能存在表示误差,不能从打印出来相同的小数位推断二进制值完全一致。应把远小于、恰好位于和明显超过门槛的情况分别纳入测试。

非有限值和业务有效性分开检查

NaN 与任何值都不会近似相等,包括另一个 NaN;正无穷只与正无穷接近,负无穷也只与自身接近。因此,两份坏掉的读数若都变成正无穷,直接比较可能返回真。示例的 reading_matches 先拒绝非有限数,再执行近似判断,避免把缺失或计算溢出的结果算成合格读数。

最后三个数展示另一条容易忽略的限制:第一个接近第二个,第二个接近第三个,并不能推出第一个接近第三个。若用近似比较直接做去重、分组或字典键等价判断,结果可能依赖处理顺序。需要分桶时应另行定义稳定的区间或量化规则。

排查失败时可以记录原始值、差值、两种阈值和单位,让接受或拒绝都有可核对的依据。不要为了消除一条失败记录不断放大容差;先查预期值和数据来源,再确认误差是否仍符合原定指标。把容差理由与测试一起保留,比只留下一个神秘的小数常量更便于维护。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。