1

我在java中实现了Babylonian/Heron的方法来获得一个数字的平方根,基于维基百科信息

目前我有:

public static void main(String[] args) {
    System.out.println(Sqrt.sqrt1(8));
    //System.out.println(Sqrt.sqrt2(8));   //Infinite loop
    System.out.println(Sqrt.sqrt3(8));
    System.out.println(Sqrt.sqrt4(8));
}

static float sqrt1(float x) {
    float b = 0, h = x;

    while (b != h) {
        b = (h + b) / 2;
        h = x / b;
    }
    return b;
}

static double sqrt2(double x) {
    double b = x, h = 0;

    while (b != h) {
        b = (h + b) / 2;
        h = x / b;
    }
    return b;
}

static double sqrt3(double x) {
    double b = x, h = 0;

    while (Math.abs(b - h) > 0.000000000001) {
        b = (h + b) / 2;
        h = x / b;
    }
    return b;
}

static double sqrt4(double x) {
    double r = x, t = 0;

    while (t != r) {
        t = r;
        r = (x / r + r) / 2;
    }
    return r;
}

输出将是:

2.828427

2.82842712474619

2.82842712474619

sqrt2 方法将永远循环,但这只会发生在双精度数上,因为 sqrt1 方法适用于浮点数。我不知道这是为什么。因此,如果我想使用双打,sqrt3 方法看起来像是要走的路。

我对我实施哪些方法有点困惑,¿巴比伦方法与苍鹭方法相同吗?

据我了解,巴比伦方法基于这样一个事实,即正方形的边是正方形(x)面积的平方根。所以你可以从一个尺寸为 bh 的矩形开始,得到两边的平均值 (b=b+h/2),然后将此结果视为较小矩形的边,当然得到另一边 (h=x /b)。矩形将开始接近所需的正方形。这是我在 sqrt1、sqrt2 和 sqrt3 方法中所做的:

while (b != h) {
    b = (h + b) / 2;
    h = x / b;
}

另一方面,维基百科链接说巴比伦/苍鹭方法是相同的,并将其描述为:

“基本思想是,如果 x 高估了非负实数 S 的平方根,那么 S/x 将被低估,因此可以合理地预期这两个数字的平均值可以提供更好的近似值”

你可以在 sqrt4 方法中看到这个实现:

    while (t != r) {  
        t = r;  
        r = (x/r + r) / 2;  
    }  

据我所知,这两种方法不一样,但相似。如果我错了,请纠正我。

有趣的是我做不到:while (b != h) {当 b 和 h 是 sqrt2 方法中所示的双精度数时,因为它将永远循环。相反,我使用while (Math.abs(b - h) > 0.000000000001) {

但是,我可以做到:while (t != r) {使用 b 和 h 加倍,如 sqrt4 所示。

我会很感激有人解释这种行为。

- - - - - -编辑: - - - - - - - - - - - - - - - - - - - ----------------------

正如所建议的,两种算法都是相同的,但实现方式不同。但是,以下建议的代码永远循环,就像 x=8 的 sqrt2 一样:

static double sqrt5(double x) {
    double b = x;

    while (b != x/b) {
        b = (x / b + b) / 2;
    }
    return b;
}

那么.. sqrt4 与其他实现有什么区别?为什么它不像其他人那样永远循环?

4

3 回答 3

5

更新: sqrt4最终退出循环的机会比sqrt5因为它的停止条件将来自一次迭代的近似值与来自它之前的迭代的近似值进行比较而具有更好的机会。

计算倾向于减少误差,因此最终计算的值非常接近与仅在最后一位不同b的确切平方根。此时,使用可用的有限精度计算的值将等于,并且迭代停止。x/bb(x / b + b) / 2b

例如,如果我们正在计算 sqrt(2) 并达到近似值 b = 1.414213562373095,我们有:

>>> b
1.414213562373095
>>> 2/b                     # Close but not quite equal to b, 
1.4142135623730951          # iteration in sqrt5 continues
>>> (2/b + b)/2        
1.414213562373095           # Exactly equal to b, sqrt4 stops

如您所见,一旦 b 达到 1.414213562373095,其值将不会因循环中的计算而改变。因为 b 和 2/b 仍然在最后一位不同,所以sqrt5永远不会退出。


Babylonian 和 Heron 的方法是相同的算法,它与求解 x²=a 的 Newton-Rhapson 方法是一致的。您拥有的各种实现之间的区别在于停止条件。例如:

while (b != h) {
    b = (h + b) / 2;
    h = x / b;
}

是相同的:

while (b != x/b) {
    b = (x / b + b) / 2;
}

当然,b != x/b这不是一个很好的停止条件,因为b它可能永远不会成为 x 的数学精确平方根:如果 b 不是精确平方根,则 x/b 不等于 b。例如在 Python 中:

>>> sqrt(2)
1.4142135623730951
>>> 2/sqrt(2)
1.4142135623730950

例如,您几乎应该总是使用相对差异的界限作为停止条件

eps = 1e-6;
while (abs(b-h)/b > eps) { ...
于 2013-03-13T18:57:07.260 回答
3

由于舍入错误,您永远不应该依赖双精度或浮点数的相等性。您需要另一种方法来确定您的近似值何时足够好。

一个好的开始是在每个循环中打印出近似值,以便您了解它是如何进行的。可能是两个连续近似值之间的差异越来越小,然后突然变得混乱。然后您知道您确实走得太远了,并返回上一个近似值,其中与先前的差异小于先前的差异。

一定要测试会产生一个永远不能用双精度表示的值的用例,比如 sqrt(2)。一定要测试应该产生可表示数字的案例。最后,确保你的函数在取平方根时会产生一个整数值。

对于浮点数和双精度数的差异:由于浮点数的精度较低,您会达到差异太小并且由于舍入误差导致 0 的点。但这只是运气,并且可能因输入不同而有所不同。但是,尾数中的位数越少,它的可能性就越大。

于 2013-03-13T18:47:58.253 回答
2

正如其他人所写的,巴比伦的方法和赫伦的方法是一回事。巴比伦人发明了它,但赫伦是第一个写下来的,几百年后,所以他得到了赞誉。有时生活并不公平。

我也同意关于精度和舍入误差的评论。但我有一个不同的解决方案要提出。通过不断地乘以或除以 4 直到它在范围内,将求平方根的数字x标准化到 1 <= x < 4 的范围内。然后“展开循环”执行计算;由于x在已知范围内,因此可以预先计算循环次数,而无需进行计算以确定何时终止循环。最后,将最初除以或乘以 4 的次数乘以或除以 2。这是代码:

function sqrt(n)
    if n < 1
        return sqrt(n * 4) / 2
    if 4 <= n
        return sqrt(n / 4) * 2
    x := (n + 1) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    return x

我将把上面的伪代码翻译成 Java 留给你。对于双打来说,循环的五次展开就足够了。由于每次循环都会使精度位数翻倍,因此循环的四次展开对于浮点数来说就足够了。

我在我的博客上讨论 Heron 的方法。

于 2013-03-13T20:52:13.893 回答