牛顿-拉夫森法计算平方根时的While循环条件

时间:2019-05-02 14:48:50

标签: java while-loop

我目前正在上一门课程,其中讲师使用以下代码在Java中实现平方根功能-

public class Sqrt { 
    public static void main(String[] args) { 

        // read in the command-line argument
        double c = Double.parseDouble(args[0]);
        double epsilon = 1.0e-15;  // relative error tolerance
        double t = c;              // estimate of the square root of c

        // repeatedly apply Newton update step until desired precision is achieved
        while (Math.abs(t - c/t) > epsilon*t) {
            t = (c/t + t) / 2.0;
        }

        // print out the estimate of the square root of c
        System.out.println(t);
    }

}

但是,如果我将while循环条件稍微更改为while (Math.abs(t - (c / t)) >= epsilon)而不是while (Math.abs(t - (c / t)) >= t * epsilon),则对于某些输入(如234.0),程序将陷入无限循环。

我使用了Eclipse调试器,发现我的代码在某个点之后返回的值t接近234的平方根,但仍然大于EPSILON。并使用更新公式,在每次迭代后产生相同的t值,因此循环永远卡在那里。

有人可以解释为什么在使用>= EPSILON时程序会失败,但是在使用>= t * EPSILON时却能正常工作吗?根据我的理解,鉴于EPSILON的极小值,t * EPSILON最终不应与EPSILON有所不同,但是在程序中实现时,差异却很大。

2 个答案:

答案 0 :(得分:1)

您实际上可以使用调试器来查看数字的进展情况,以及为什么在epsilon不乘以t的情况下,为什么234的平方根会导致无休止的循环。

我已经将IntelliJ与记录断点一起使用,以查看数字如何进行以及无止境循环发生的原因:

enter image description here

首先,我在记录断点中使用了此表达式:

" " + Math.abs(t - c/t) + " " + epsilon

此代码:

private static void calcRoot(String arg) {
    // read in the command-line argument
    double c = Double.parseDouble(arg);
    double epsilon = 1.0e-15;  // relative error tolerance
    double t = c;              // estimate of the square root of c

    // repeatedly apply Newton update step until desired precision is achieved
    while (Math.abs(t - c/t) > epsilon ) {
        t = (c/t + t) / 2.0;
    }

    // print out the estimate of the square root of c
    System.out.println(t);
}

这是证明事实epsilon小于Math.abs(t - c/t)且此Math.abs(t - c/t)停止前进的结果:

 233.0 1.0E-15
 115.50851063829788 1.0E-15
 55.82914775415816 1.0E-15
 24.47988606961853 1.0E-15
 7.647106514310517 1.0E-15
 0.927185521197492 1.0E-15
 0.014043197832668497 1.0E-15
 3.2230278765865705E-6 1.0E-15
 1.723066134218243E-13 1.0E-15
 1.7763568394002505E-15 1.0E-15
 1.7763568394002505E-15 1.0E-15
 1.7763568394002505E-15 1.0E-15
 1.7763568394002505E-15 1.0E-15
 1.7763568394002505E-15 1.0E-15
 1.7763568394002505E-15 1.0E-15
 1.7763568394002505E-15 1.0E-15
 ...

如果我随后使用epsilon * t并将日志记录表达式更新为" " + Math.abs(t - c/t) + " " + epsilon * t,我会看到完全不同的控制台输出:

 233.0 2.34E-13
 115.50851063829788 1.175E-13
 55.82914775415816 5.974574468085106E-14
 24.47988606961853 3.1831170803771985E-14
 7.647106514310517 1.959122776896272E-14
 0.927185521197492 1.5767674511807463E-14
 0.014043197832668497 1.5304081751208715E-14
 3.2230278765865705E-6 1.529706015229238E-14
 1.723066134218243E-13 1.5297058540778443E-14

更新

如果您对BigDecimal类尝试相同的操作,则在选择足够的舍入位数的情况下,将能够计算234的平方根(请参见下面的scale变量) ):

private static void calcRootBig(String arg) {
    // read in the command-line argument
    BigDecimal c = new BigDecimal(arg);
    BigDecimal epsilon = new BigDecimal(1.0e-15);  // relative error tolerance
    BigDecimal t = new BigDecimal(c.toString());              // estimate of the square root of c
    BigDecimal two = new BigDecimal("2.0");

    // repeatedly apply Newton update step until desired precision is achieved
    int scale = 10;
    while (t.subtract(c.divide(t, scale, RoundingMode.CEILING)).abs().compareTo(epsilon) > 0) {
        t = c.divide(t, scale, RoundingMode.CEILING).add(t).divide(two, scale, RoundingMode.CEILING);
    }

    // print out the estimate of the square root of c
    System.out.println(t);
}

但是,如果您只选择3作为四舍五入比例,那么您将陷入无限循环中。

因此,看来浮点除法的精度实际上在您的情况下导致了无休止的循环。 epsilon * t的乘积只是克服默认浮点运算中缺乏舍入精度的一种技巧。

答案 1 :(得分:0)

double的精度约为15位(或1到2 ^ 52或4.5e15)。当您计算t * epsilon时,就要求1e15/234可能会出现1到double的误差,而当您使用epsilon时,您要求的误差是1到{{ 1}},它是double精度的极限,除非它是一个精确值且错误为1e15。例如为0尝试此操作,它可能会起作用,但是任何不完全相同的值都可能无效。

对任意端点的一种简单解决方案是,一旦错误从一次迭代到下一次迭代没有改善,就停止。这将为您提供使用此公式的最准确解决方案。