为何Numpy采用的成对求和比朴素求和的误差更小?
为什么成对求和比朴素求和的数值误差更小?
Numpy的sum函数采用成对求和而非朴素求和,以此获得更优的数值误差表现,下面就直观拆解成对求和降低误差的核心逻辑。
朴素求和
朴素求和是按顺序依次累加,计算形式为:
sum = (((x1+x2)+x3)+…+xn)
这种方式的平均误差为O(√N),最坏情况误差可达O(N)。问题出在:当累加过程中出现大数加小数时,小数的尾数会因浮点数的精度限制被截断,每次截断产生的误差会随着累加次数线性累积。
成对求和
成对求和通过递归两两配对相加来降低误差,步骤如下:
- 初始配对:以原始数字列表为起点:
{x1,x2,x3,x4,…,xn} - 成对求和:将列表中的数字两两求和:
{(x1+x2),(x3+x4),…} - 递归求和:重复上述步骤,直到得到单个求和结果
这种方式的平均误差为O(log√N),最坏情况误差为O(logN),误差量级远低于朴素求和。
核心改进点:减少误差累积的次数与量级
浮点数相加的误差根源是尾数对齐时的截断:当两个数的量级差距超过浮点数的尾数位数时,小数会被完全“吃掉”(比如3位尾数下,1e10 + 1e-10的结果还是1e10)。
朴素求和的问题
在朴素求和中,如果先加一个大数,后续所有小数都会被这个大数“掩盖”,每加一个小数就产生一次截断误差,误差累积次数是线性的(O(N))。比如用3位尾数计算1e10 + 1e-10 + 1e-10 + ... + 1e-10(1000个小数),每次加小数都被截断,最终结果还是1e10,总误差是1e-7,完全丢失了所有小数的贡献。
成对求和的优势
成对求和优先让量级相近的数先相加:
- 先把所有小数两两相加,逐步得到更大的数(比如1000个1e-10先加成500个2e-10,再成250个4e-10……直到得到
1e-7) - 这时再和
1e10相加,虽然还是有截断,但至少保留了小数的总贡献,误差只产生一次,而非1000次
另外,递归配对的方式让误差的累积次数变成对数级:N个数需要log₂N轮配对,每轮的误差只会在当前层级累积,不会像朴素求和那样线性叠加。
补充例子(更能体现差异)
用3位尾数计算:1e10 + 1e-10 * 1000
- 朴素求和:
(((1e10 + 1e-10) + 1e-10) + ...) = 1e10,总误差1e-7 - 成对求和:先把1000个1e-10配对累加得到
1e-7,再和1e10相加,结果为1.0000000000001e10(3位尾数下显示为1.00e10,但实际保留了更多有效信息),误差远小于朴素求和。
内容的提问来源于stack exchange,提问作者progr
相关产品推荐
相关产品推荐

