我的trapezoid rule(梯形法则)实现为何未得到预期结果?
梯形法则计算偏差问题排查
我实现了一个用于计算给定函数梯形法则结果的函数,该函数在计算以下函数时结果偏差较大:
$f(x) = \log_2(x)$
当我使用n < 8的参数计算梯形法则结果时,输出值远大于实际面积,这不符合我的预期:我已经绘制了f(x)的图像并模拟了少量梯形的划分情况,理论上计算结果应该小于目标面积才对。
不过随着n取值增大,误差逐渐降低,当n = 10000000时,结果与实际值的偏差在0.001以内。
原实现代码如下:
private interface MathFunc { double apply(double value); } private static final double A = 1; private static final double B = 9; public static void main(String args[]) { MathFunc func = (x) -> Math.log(x) / Math.log(2); double realValue = 16.98776493946568; for(int i = 1; i <= 8; i*=2) { double value = trapezoidRule(A, B, func, i); System.out.println(i + " Trapezoid Summation for f(x): " + value); double absError = Math.abs(value - realValue); System.out.println("Abs Error: " + absError); System.out.println("% Error: " + (absError/realValue)*100); System.out.println(); } } static double trapezoidRule(double a, double b, MathFunc f, double n) { double deltaX = (b-a)/n; double i = 0; double sum = 0.0; while( i++ <= n ) { if(i == 0 || i == n) { sum += f.apply(a + (i*deltaX)); } else { sum += 2 * f.apply(a + (i*deltaX)); } } return (deltaX * sum) / 2.0; }
问题原因
- 循环控制逻辑错误:使用后置自增
i++ <= n作为循环判断条件,进入循环体时的i值已经完成自增,不仅i == 0的条件永远无法命中,左端点函数值被错误按照中间点规则计入2次,还会多执行一次循环,额外计算了超出区间上限的函数值,直接拉高了求和结果 - 索引范围不符合算法要求:标准梯形法则需要计算的点索引范围是0到n,仅两端点按原值累加,中间点按2倍值累加,错误实现中实际计算的点索引是1到n+1,完全不符合算法要求
- 当n取值很大时,额外多算的一个点和左端点的系数误差占总求和的比例被稀释,因此误差会逐渐收敛到合理范围,和你观测到的现象一致
修正后的实现
static double trapezoidRule(double a, double b, MathFunc f, double n) { double deltaX = (b - a) / n; // 先单独累加两个端点的函数值 double sum = f.apply(a) + f.apply(b); // 遍历所有中间点,按2倍值累加 for (int i = 1; i < n; i++) { sum += 2 * f.apply(a + i * deltaX); } return sum * deltaX / 2.0; }
修正后小n值的计算结果就会符合预期,误差特性也会和梯形法则的理论收敛性匹配。
内容的提问来源于stack exchange,提问作者Rawley Fowler
相关产品推荐
相关产品推荐

