我正在尝试使用Newton方法的不同实现来计算平方根。一个重要的决定是什么时候终止算法。
显然,使用y*y
和x
之间的绝对差异是不行的,其中y
是x
的平方根的当前估计值,因为对于值x
可能无法以足够的精度表示其平方根。
所以我应该使用相对标准。天真的我会用这样的东西:
static int sqrt_good_enough(float x, float y) {
return fabsf(y*y - x) / x < EPS;
}
这看起来效果很好。但是最近我开始阅读Kernighan和Plauger的编程风格元素,他们在第1章中给出了相同算法的Fortran程序,其终止标准(用C翻译)将是:
static int sqrt_good_enough(float x, float y) {
return fabsf(x/y - y) < EPS * y;
}
两者在数学上是等价的,但是有理由选择一种形式而不是另一种吗?
答案 0 :(得分:5)
他们仍然不等同;底部数字在数学上等同于fabsf(y*y - x) / (y*y) < EPS
。我看到你遇到的问题是,如果y*y
溢出(可能是因为x
FLT_MAX
而且y
被选中),那么终止可能永远不会发生。以下交互使用了双打。
>>> import math
>>> x = (2.0 - 2.0 ** -52) * 2.0 ** 1023
>>> y = x / math.sqrt(x)
>>> y * y - x
inf
>>> y == 0.5 * (y + x / y)
True
编辑:作为评论(现已删除)指出,在迭代和终止测试之间共享操作也很好。
EDIT2:两者都可能存在子正常x
的问题。 professionals规范化x
以避免两种极端情况的并发症。
答案 1 :(得分:3)
这两个实际上并不完全等效,除非你写fabsf(y * y - x)/(y * y)&lt; EPS的第一个。 (对不起我原来评论中的拼写错误)
但我认为关键点在于使这里的表达式与您在牛顿迭代中计算y的公式相匹配。例如,如果y公式为y =(y + x / y)/ 2,则应使用Kernighan和Plauger的样式。如果是y =(y * y + x)/(2 * y),你应该使用(y * y - x)/(y * y)&lt; EPS。
通常,终止标准应该是abs(y(n + 1)-y(n))足够小(即小于y(n + 1)* EPS)。这就是两个表达式应该匹配的原因。如果它们不完全匹配,则由于不同的缩放,终止测试可能决定残差不够小而y(n)的差小于浮点误差。结果将是无限循环,因为y(n)已停止更改并且永远不会满足终止条件。
例如,下面的Matlab代码与第一个示例完全相同,但它永远运行:
x = 6.800000000000002
yprev = 0
y = 2
while abs(y*y - x) > eps*abs(y*y)
yprev = y;
y = 0.5*(y + x/y);
end
它的C / C ++版本存在同样的问题。