我已经实现了此功能:
double heron(double a)
{
double x = (a + 1) / 2;
while (x * x - a > 0.000001) {
x = 0.5 * (x + a / x);
}
return x;
}
此功能正在按预期工作,但是我希望对其进行改进。应该使用无穷的while循环来检查类似于x * x
的东西是否是a
。 a
是用户应输入的数字。
到目前为止,我没有使用该方法的工作功能...这是我惨败的尝试:
double heron(double a)
{
double x = (a + 1) / 2;
while (x * x != a) {
x = 0.5 * (x + a / x);
}
return x;
}
这是我的第一篇文章,因此,如果有任何不清楚或需要补充的内容,请告诉我。
尝试失败2:
double heron(double a)
{
double x = (a + 1) / 2;
while (1) {
if (x * x == a){
break;
} else {
x = 0.5 * (x + a / x);
}
}
return x;
}
答案 0 :(得分:5)
应该使用无休止的
while
循环来检查与x * x
类似的事物是否是a
问题:
收敛缓慢
当最初的x
错误时,改进的|x - sqrt(a)|
错误可能仍然只有原来的一半。考虑到double
的范围很广,可能需要数百次迭代才能接近。
参考:Heron's formula。
对于一种新颖的第一估算方法:Fast inverse square root。
溢出
x * x
中的 x * x != a
容易溢出。 x != a/x
进行了类似的测试,没有出现范围问题。如果发生溢出,x
可能会被“无限”或“非数字”“感染”而无法收敛。
振荡
一旦x
与sqrt(a)
“接近”(在2倍之内),错误收敛将是二次的-“ right”的位数将使每次迭代加倍。这种情况一直持续到x == a/x
为止,或者由于double
数学的特殊性,x
会像商一样在两个值之间不断振荡。
进入此振荡会导致OP的循环不会终止
将其与测试工具一起使用,证明了足够的收敛性。
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
double rand_finite_double(void) {
union {
double d;
unsigned char uc[sizeof(double)];
} u;
do {
for (unsigned i = 0; i < sizeof u.uc; i++) {
u.uc[i] = (unsigned char) rand();
}
} while (!isfinite(u.d));
return u.d;
}
double sqrt_heron(double a) {
double x = (a + 1) / 2;
double x_previous = -1.0;
for (int i = 0; i < 1000; i++) {
double quotient = a / x;
if (x == quotient || x == x_previous) {
if (x == quotient) {
return x;
}
return ((x + x_previous) / 2);
}
x_previous = x;
x = 0.5 * (x + quotient);
}
// As this code is (should) never be reached, the `for(i)`
// loop "safety" net code is not needed.
assert(0);
}
double test_heron(double xx) {
double x0 = sqrt(xx);
double x1 = sqrt_heron(xx);
if (x0 != x1) {
double delta = fabs(x1 - x0);
double err = delta / x0;
static double emax = 0.0;
if (err > emax) {
emax = err;
printf(" %-24.17e %-24.17e %-24.17e %-24.17e\n", xx, x0, x1, err);
fflush(stdout);
}
}
return 0;
}
int main(void) {
for (int i = 0; i < 100000000; i++) {
test_heron(fabs(rand_finite_double()));
}
return 0;
}
改进
sqrt_heron(0.0)
有效。
更改代码以获得更好的初始猜测。
double sqrt_heron(double a) {
if (a > 0.0 && a <= DBL_MAX) {
// Better initial guess - halve the exponent of `a`
// Could possible use bit inspection if `double` format known.
int expo;
double significand = frexp(a, &expo);
double x = ldexp(significand, expo / 2);
double x_previous = -1.0;
for (int i = 0; i < 8; i++) { // Notice limit moved from 1000 down to < 10
double quotient = a / x;
if (x == quotient) {
return x;
}
if (x == x_previous) {
return (0.5 * (x + x_previous));
}
x_previous = x;
x = 0.5 * (x + quotient);
}
assert(0);
}
if (a >= 0.0) return a;
assert(0); // invalid argument.
}