Javaでのニュートン法による平方根

Nov 06 2020

今回、私の研究室の1つは、ニュートン法を介して数値の平方根を計算する完全なJavaプログラムを作成する必要があると述べています。とMath.sqrt(x)は対照的に、ニュートン法を使用する理由は、単純なIO、条件式、ループ、およびネストされたループの使用を練習するためです。

手順の完全なセットは次のとおりです。

xの平方根を計算するとします。

最初に:xの任意の値の平方根がy = 1.0であるという推測/近似から常に開始します

次に、このy値とx値の平均をy値で割って計算します。

この方程式→(y +(x / y))/ 2。

この方程式を解いた結果は、平方根の新しい近似(新しいy値)になります。この新しいy値は、元のy推定値1.0よりもxの平方根の実際の値に近くなります。

yの値が十分に近くなるまで、xの平方根の新しい推測としてyの新しい計算値を使用して、上記の手順を繰り返します。

例:2の平方根を計算したい場合、計算は次のように進行します(すべての値はdoubleとして):x = 2.0およびy = 1.0

Iteration     Guess   Quotient                 Average
Number        for y   x/y                      ( y + (x/y) ) / 2

        1       1.0   2.0/1.0 => 2.0           (1.0 + 2.0/1.0)/2.0 => 1.5
        2       1.5   2.0/1.5 => 1.33333       (1.5 + 1.33333)/2.0 => 1.41666
        3   1.41666   2.0/1.41666 => 1.41176   (1.41666 + 1.41176)/2.0 => 1.41421
        4   1.41421   etc...

推測の2乗がxの値(平方根の定義)と「同じ」である場合に、解が得られます。残念ながら、ほとんどの場合、この方法で正確な解決策に到達することはできないため、プロセスは永遠に続きます。計算プロセスが実際に停止することを保証するために、ループの停止条件を「十分に近い」ように変更する必要があります。閉じる十分である寛容我々は正確な値の良好な近似値であるとして受け入れる(少数)。

xと(y * y)の絶対差が許容値(ユーザーが定義)よりも小さい場合、十分に接近します。

プログラムの進行状況を確認するには、反復回数と推測値を出力します。10進数の後に少なくとも8桁、10進数の前に2桁を使用してください。以下に示す実行例では、小数点以下5桁のみが使用されています。

実行例:

Square Root approximation program

Enter the value           : 2.0
Enter the tolerance       : 0.00001

Iteration     Guess      Guess     Absolute value
Number        value      Squared       Difference

        1   1.00000      1.00000          1.00000 
        2   1.50000      2.25000          0.25000 
        3   1.41666      2.00693          0.00693 
        4   1.41421      1.99999          0.00001 

Approximated Square root of 2.0 = 1.41422 

この問題を解決するために私が書いた結果のコードは次のとおりです。

import java.util.Scanner;

/**
 * SRN: 507-147-9
 */
public class Lab8_1 {

    public static void main(String[] args) {

        // new input
        Scanner input = new Scanner(System.in);

        // define vars
        int iteration = 1;
        double x, y, guessSquared = 1, quotient, average, tolerance, absValDiff = 1;

        y = 1.0;

        // program name
        System.out.println("Square Root approximation program");
        System.out.println();

        // prompt variables
        System.out.print("Enter the value           : "); // prompt x value
        x = input.nextDouble(); // store input as "x"
        System.out.print("Enter the tolerance       : "); // prompt tolerance
        tolerance = input.nextDouble(); // store input as "tolerance"
        System.out.println();

        // print formatted header
        System.out.println("Iteration         Guess          Guess         Absolute value");
        System.out.println("Number            value          Squared           Difference");
        System.out.println();

        // print first calculation
        System.out.printf("%9d   %11.8f      %11.8f          %11.8f \n", iteration, y, guessSquared, absValDiff);

        // increment the value of "iteration" by 1
        iteration++;

        while (absValDiff > tolerance) {

            // looped calculations
            quotient = x / y;
            average = (y + quotient) / 2;
            y = average;
            guessSquared = y * y;
            absValDiff = Math.abs(x - guessSquared);

            // print results per iteration
            System.out.printf("%9d   %11.8f      %11.8f          %11.8f \n", iteration, y, guessSquared, absValDiff);

            // increment the value of "iteration" by 1
            iteration++;
        }
        // print results
        System.out.println();
        System.out.printf("Approximated Square root of " + x + " = %11.8f \n", y);
    }
}

これで、例の応答とは対照的に、応答の正確さに関して質問があります。数学的には正しい浮動小数点数が変数に格納されていますが、結果がコンソールに出力されると、浮動小数点の最後の桁は常に切り上げられます。私の見解では、これは電卓で発生する丸め誤差に似ています。計算の結果は実際の数値の近似表現にすぎません。これは、固定小数点の有理数のみがマシン内で正確に表現できるためです。

この種のエラーの例は次のようになります:2/3 = 0.666666667

printfコンソールに出力するときに数値を切り上げることなく、フォーマットを使用して変数の設定された長さをキャプチャする方法はありますか?

回答

7 GilbertLeBlanc Nov 06 2020 at 19:46

コンソールに出力するときに数値を切り上げることなく、printf形式を使用して変数の設定された長さをキャプチャする方法はありますか?

あなたは「トリック」を使うことができます

String output = String.format("%12.9f", doubleValue);
System.out.println(output.substring(0, output.length() - 1);
6 htl Nov 06 2020 at 22:01

あなたのプログラムは今のところかなり良さそうです。これが私が気づいたいくつかのことです:

  • を計算するとき、最初の反復で論理エラーが発生しますabsValDiff。常にabsValDiff1に初期化し、最初の反復では再計算しないため、x = 5などの値を入力すると、最初の推測(y = 1)の2乗とxの差は1になります。実際には4である必要があります。

  • 上記の結果として、これは、1以上の許容誤差を要求しても正しく機能せず、プログラムがすぐに終了することも意味します。たとえば、許容値2を要求しても、正しく機能しません。

  • 他の読者にとってより役立つようにコメントを書くことができるかもしれません。コースで特に指示がない限り、コードを読んでいる人は言語の機能を理解しており、コードがその手順を実行している理由に関心があると見なすことができます。例えば:

    // define vars
    int iteration = 1;
    double x, y, guessSquared = 1, quotient, average, tolerance, absValDiff = 1;
    

    ここで変数を定義していることは明らかなので、代わりに何をしているのかを説明してみることができます。変数の目的を読者に伝えてみませんか?

  • 通常、メソッドの先頭ですべてを宣言するよりも、必要に応じて変数を宣言する方が簡単です。詳細については、こちらの宣言に関するセクションを参照してください。そのソースで述べたように、古いプログラミング言語ではすべての変数を前もって宣言するのが伝統でしたが、今それを行うことは一般的にベストプラクティスとは見なされていません。

これらすべてを変更すると、コードは次のようになります。

import java.util.Scanner;

/**
 * SRN: 507-147-9
 */
public class Lab8_1 {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int iteration = 1; // Counts the current iteration number at each step.
        double y = 1.0; // Initial guess for the square root of the number x.
        double guessSquared = y * y; // The value y^2 which we want to be close to x.

        System.out.println("Square Root approximation program");
        System.out.println();

        System.out.print("Enter the value           : ");
        // The number we intend to approximate the square root of.
        double x = input.nextDouble(); 
        System.out.print("Enter the tolerance       : ");
        // The tolerance so that we terminate if |y^2 - x| < tolerance.
        double tolerance = input.nextDouble();
        System.out.println();
        
        // Compute the initial difference between x and the square of
        // the approximate square root.
        double absValDiff = Math.abs(x - guessSquared);

        // Print formatted header.
        System.out.println("Iteration         Guess          Guess         Absolute value");
        System.out.println("Number            value          Squared           Difference");
        System.out.println();

        // Print the results for the first iterate
        System.out.printf("%9d   %11.8f      %11.8f          %11.8f \n", iteration, y, guessSquared, absValDiff);
        iteration++;

        // Iterate using Newton's method until we obtain a value within tolerance of
        // the true square root.
        while (absValDiff > tolerance) {
            // Calculate the new approximation for x using 
            // Newton's method, where given the previous approximation y,
            // the next approximation is given by (y + (x / y)) / 2.
            double quotient = x / y;
            y = (y + quotient) / 2;
            guessSquared = y * y;
            // Compute the new difference between the square of our approximation and x.
            absValDiff = Math.abs(x - guessSquared);

            // Print results per iteration.
            System.out.printf("%9d   %11.8f      %11.8f          %11.8f \n", iteration, y, guessSquared, absValDiff);
            iteration++;
        }
        // Print results for the final iteration which was in tolerance.
        System.out.println();
        System.out.printf("Approximated Square root of " + x + " = %11.8f \n", y);
    }
}

丸めを回避する(代わりに数値を切り捨てる)ために、Gilbert Le Blancの方法の代替アプローチが必要な場合は、ここで説明するようにDecimalFormatを使用できます。とをインポートした後、次のように使用できます。java.text.DecimalFormatjava.math.RoundingMode

DecimalFormat df = new DecimalFormat("#.##");
df.setRoundingMode(RoundingMode.DOWN);
System.out.println(df.format(0.66999f));

これはを出力します0.66。