功能说明
浮点数只有有限有效位,累加一系列量级差异很大的数时,小值可能被大值吞掉。经典例子是一亿亿、一、负一亿亿、一依次相加:前两步得到一亿亿,第三步回到零,最后一步才得到一,最终结果为一;但数学上答案应为二。Kahan 补偿求和用一个额外变量记录每步被舍入掉的低位误差,在后续步骤中补偿回来。Neumaier 变体进一步改进,在加数绝对值大于累加和时采用另一条补偿公式,能正确处理 Kahan 失效的场景。
两个函数都只遍历一次,空间恒定,适合流式计算和财务汇总的中间步骤。
关键代码
kahan_sum 先从上一步的补偿值中减去误差得到 adjusted,再加到 total。关键在第三行:用更新后的 total 减去旧 total 再减去 adjusted,恰好提取出本次加法被舍入的误差,存入 compensation 供下一次使用。neumaier_sum 先用 updated 保存朴素和,再比较 total 与 value 的绝对值,选择不会丢失低位的那条差值公式累加补偿,最后统一把补偿加回总和。main 用同一组极端数据对比三种写法。
使用注意
补偿求和不能修复输入本身已被解析丢失的精度,也不能替代高精度十进制计算。涉及金额时更应使用整数分或 Decimal,而不是依赖浮点补偿。Kahan 对上述强抵消序列仍可能返回零,因此本例同时给出 Neumaier。累加顺序仍会影响结果,排序后再加通常更稳定。若数据规模很大且允许两遍遍历,math.fsum 是更简单可靠的选择。
验证方式
运行脚本,naive 输出 1.0,kahan 也输出 1.0,说明经典 Kahan 在该序列上仍然丢掉了第二个一;neumaier 输出 2.0,与数学结果一致。再把序列改为一亿亿、一、负一亿亿、一、一,观察 Neumaier 是否仍能保留所有小数。
代码示例
def kahan_sum(values: list[float]) -> float:
total = 0.0
compensation = 0.0
for value in values:
adjusted = value - compensation
updated = total + adjusted
compensation = (updated - total) - adjusted
total = updated
return total
def neumaier_sum(values: list[float]) -> float:
total = 0.0
compensation = 0.0
for value in values:
updated = total + value
if abs(total) >= abs(value):
compensation += (total - updated) + value
else:
compensation += (value - updated) + total
total = updated
return total + compensation
if __name__ == "__main__":
values = [1e16, 1.0, -1e16, 1.0]
print("naive:", sum(values))
print("kahan:", kahan_sum(values))
print("neumaier:", neumaier_sum(values))