【问题标题】:Using Heron's formula to calculate square root in C使用 Heron 公式计算 C 中的平方根
【发布时间】:2018-11-16 23:14:34
【问题描述】:

我已经实现了这个功能:

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的东西是否是aa 是用户应该输入的数字。

到目前为止,我还没有使用该方法的工作功能...这是我惨败的尝试:

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;
}

Heron's formula

【问题讨论】:

  • 你永远不应该真正测试浮点变量是否相等,尤其是非理性值。在大多数情况下,您的第二个函数可能会永远循环。此外,还有更快的计算平方根的方法,但您已经知道了,对吧?
  • while (x * x != a) 很可能总是正确的,除了一些具有精确表示的值。第一个示例使用 epsilon 容差是有原因的。请参阅Is floating point math broken?Why Are Floating Point Numbers Inaccurate?
  • 很少有测试浮点变量是否相等的好时机。相等表示循环完成。附带条件是平等测试不是完成的唯一测试。 @WeatherVane 第一个代码的x * x - a > 0.000001 对FP 函数的某些epsilon 的使用不当。对于x 的大值是没有意义的,对于x 的小值来说太大了。对某些 epsilon 的相对使用在这里可能有意义,但不是绝对的编码。

标签: c square-root


【解决方案1】:

它应该使用无穷无尽的while 循环来检查类似于x * x 的东西是否是a

问题:

收敛缓慢

当最初的x 完全错误时,改进后的|x - sqrt(a)| 错误可能仍然只有一半大。鉴于double 的范围很广,可能需要数百次 次迭代才能接近。

参考:Heron's formula.

对于一种新颖的第一种估计方法:Fast inverse square root

溢出

x * x != a 中的x * x 容易溢出。 x != a/x 提供了一个没有该范围问题的类似测试。如果发生溢出,x 可能会被“infinity”或“not-a-number”“感染”而无法收敛。

振荡

一旦xsqrt(a)“接近”(在 2 倍以内),误差收敛是二次的 - 每次迭代“正确”的位数加倍。这种情况一直持续到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.
}

【讨论】:

  • 建议在assert(0); 之前增加1000 循环限制(可能是2000?)使用1000"sqrt_herons_formula: sqrt_herons_formula.c:38: sqrt_heron: Assertion '0' failed."(我不确定您将如何退出最佳限制, 2000 代码在 ~3 分 37 秒内完成)
  • 改进的sqrt_heron 将其缩短到大约 14 秒。令人印象深刻。
  • @david 1000 来自double 的最大 2 次方系数。 (例如 10e308)。嗯,也许还有其他事情失败了?在我的机器上,最大迭代次数约为 520。你呢?
  • @DavidC.Rankin sqrt 例程就像三角函数。一旦我们处于“主要”范围内,要使用的算法就会被很好地教授、研究和编码。将整个 FP 范围内的原始x 争论到“主要”范围通常是更难正确解决的问题。在my_sqrt() 的情况下,与便携式、笨重的frexp(), ldexp() 或OP 的弱(a + 1) / 2 相比,需要一些非便携式作弊来快速将指数减半。
  • 现在运行计算。看看max 最终出现在我的盒子上和你的盒子上会很有趣。我唯一能想到的是浮点单元处理数学的方式有一些奇怪的差异。 (仍在等待 3 分钟 37 秒通过。tic tic tic tic)Max 在我的盒子上是 1022
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-03
  • 1970-01-01
  • 1970-01-01
  • 2017-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多