使用 Heron 公式计算 C 中的平方根
Using Heron's formula to calculate square root in C
我实现了这个功能:
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;
}
这是我的第一个 post 所以如果有任何不清楚的地方或我应该补充的东西请告诉我。
失败次数 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;
}
It's supposed to use and endless while
loop to check if something similar to x * x
is a
问题:
收敛速度慢
当最初的x
错误很大时,改进后的|x - sqrt(a)|
错误可能仍然只有原来的一半。鉴于 double
的广泛范围,可能需要 数百次 次迭代才能接近。
参考:Heron's formula.
对于新颖的第一种估计方法:Fast inverse square root。
溢出
x * x
in x * x != a
容易溢出。 x != a/x
提供类似的测试,没有范围问题。如果发生溢出,x
可能会与 "infinity" 或 "not-a-number" 得到 "infected" 而无法实现收敛。
振荡
一旦 x
是 "close" 到 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.
}
我实现了这个功能:
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;
}
这是我的第一个 post 所以如果有任何不清楚的地方或我应该补充的东西请告诉我。
失败次数 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;
}
It's supposed to use and endless
while
loop to check if something similar tox * x
isa
问题:
收敛速度慢
当最初的x
错误很大时,改进后的|x - sqrt(a)|
错误可能仍然只有原来的一半。鉴于 double
的广泛范围,可能需要 数百次 次迭代才能接近。
参考:Heron's formula.
对于新颖的第一种估计方法:Fast inverse square root。
溢出
x * x
in x * x != a
容易溢出。 x != a/x
提供类似的测试,没有范围问题。如果发生溢出,x
可能会与 "infinity" 或 "not-a-number" 得到 "infected" 而无法实现收敛。
振荡
一旦 x
是 "close" 到 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.
}