質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.21%

ニュートン法でx^2-2=0を解くアルゴリズム

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,872

Watanabe_ef

score 28

ニュートン法でx^2-2=0を解くアルゴリズムを作成しております。

下記のコードを実行した場合、1回目の誤差の表示はうまくいくのですが、2回目のWhile文の判定で
エラーが起きます。

引数には2を渡しています。

import java.math.BigDecimal;

public class kinjiti {
    public static void main(String args[]) {
        BigDecimal x = new BigDecimal(args[0]);
        BigDecimal delta = new BigDecimal(1);
        BigDecimal sisu = new BigDecimal(1.0E-16);
        BigDecimal j = new BigDecimal(2);
        int count = 0;
        while (sisu.compareTo(delta) < 0) {
         x = x.subtract((x.multiply(x).subtract(j)).divide((j.multiply(x))));
         delta = x.multiply(x).subtract(j).abs();
         ++count;
         System.out.println(count + "回目 誤差=" + delta);
        }
        System.out.println("x = " + x);
        System.out.println("x * x = " + x.multiply(x));
    }
}

【コンソール表示内容(エラー内容)】
1回目 誤差=0.25
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.base/java.math.BigDecimal.divide(BigDecimal.java:1716)
at Newton1.main(Newton1.java:12)

どうして1回目は計算できているのに2回目でエラーが起きるのかわかりません。

ご教示お願いいたします。

【追記】
上記のとおりプログラムを修正したところ、2の平方根が正しく出力されました。

【コンソール】
1回目 誤差=0.2500
2回目 誤差=0.00703889
3回目 誤差=0.0000061592637476
4回目 誤差=4.74200288533215216656E-12
5回目 誤差=2.8108239331373466350562834003132792751609E-24
6回目 誤差=9.8759139788721286232385069031337583760272284804383992169099233938623991426116900E-49
7回目 誤差=1.219170961476023987580884253005982643084857060464517172599001310224667466278282886591601613436503357212794231959984165464850947330556127739804351086766287896576E-97
x = 1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764161583921656050189505811755022976
x * x = 2.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001219170961476023987580884253005982643084857060464517172599001310224667466278282886591601613436503357212794231959984165464850947330556127739804351086766287896576

しかし、ソースの[x][j]に引数3.0を指定すると、誤差の表示がループします。
コンソールに誤差が7回ほど出力され、「x = 1.3720508......」と出力される想定でした。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

こんにちは
(ほかの方の回答にありますが)BigDecimalではdevideの時計算結果が循環小数の場合,
エラーが出るため,丸める必要があります。(四捨五入や切り上げなど)
私は,四捨五入で計算しましたが
https://java-reference.com/java_number_round.htmlを参考に自分のお好きなものを使って
いろいろ試してみてください.
以下 ソースコードとなります。

import java.math.BigDecimal;

public static void main(String args[]) {

        BigDecimal x = new BigDecimal("2.0");
        BigDecimal delta = new BigDecimal("1.0");
        BigDecimal sisu = new BigDecimal("1.0E-16");
        BigDecimal j = new BigDecimal("2.0");
        int count = 0;
        while (sisu.compareTo(delta) < 0) {
            BigDecimal a = (x.multiply(x).subtract(j)).divide(x.multiply(j),BigDecimal.ROUND_HALF_UP);
            x = x.subtract(a);
            delta = (x.multiply(x).subtract(j)).abs();
            count++;
         System.out.println(count + "回目 誤差=" + delta);
        }
        System.out.println("x = " + x);
        System.out.println("x * x = " + x.multiply(x));
    }
}


(追記)
aの平方根を求めるときのソースとなります。(入力)

import java.util.Scanner;
import java.math.BigDecimal;

public class Sum {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        String line = sc.nextLine();
        Double n = Double.parseDouble(line);
        BigDecimal a = new BigDecimal(n); //√aを求めたい
        BigDecimal x = new BigDecimal("2.0");
        BigDecimal delta = new BigDecimal("1.0");
        BigDecimal sisu = new BigDecimal("1.0E-16");

        int count = 0;
        while (sisu.compareTo(delta) < 0) {
            BigDecimal f = x.multiply(x).subtract(a);
            BigDecimal df = x.multiply(new BigDecimal("2.0")); 
            x = x.subtract(f.divide((df),BigDecimal.ROUND_HALF_UP));
            delta = (x.multiply(x).subtract(a)).abs();
            count++;
         System.out.println(count + "回目 誤差=" + delta);
        }
        System.out.println("x = " + x);
        System.out.println("x * x = " + x.multiply(x));
    }


}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/23 14:05

    回答ありがとうございます。
    回答のとおりに丸めたところ、エラーはなくなりました。ありがとうございます。
    当初の質問とは変わってしまい申し訳ないのですが、追加で質問です。
    今回は引数に「2」を渡しており、結果も正常に出力されました。
    引数に「3」を渡した場合、3の平方根の値がコンソールに出力される想定なのですが、
    うまく出力されません。(編集で追記しておきます)
    お時間がありましたら、回答いただけますと幸いです。よろしくお願いします。

    キャンセル

  • 2018/03/23 15:26 編集

    aの平方根の値を求めるためにaを入力する必要があります
    上の回答に追記したのでそちらをご覧ください。
    あとソースを書いていく途中 気づいたのですが
    値が2である変数jを分子 分母 両方に入れています。
    2の平方根を求める場合だけたまたま同じで
    一般的には違うので変数を分ける必要があると思います。
    改良したソースでは分けています。(正確に言うと2のところは変数にしてません...)
    (一般の時(x ^ 2 - a) / (2 * x) で
    a = 2の時だけ 分子 分母 両方に2が使われているという意味です。)

    キャンセル

  • 2018/03/23 16:10

    回答ありがとうございます。
    勘違いをしておりました。2でたまたま通っていただけでしたね…。
    追記のソース確認いたしました。想定通りの結果が出ました。
    本当にありがとうございました。

    キャンセル

  • 2018/03/23 16:41

    私もニュートン法について深めることができてよかったです!!

    キャンセル

0

これですかね。BigDecimal.divide() 使用時の注意

BigDecimalクラスの割り算を行うdivide()メソッドにて、計算結果が循環小数になる場合
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
が発生するので注意が必要。

ただし、丸め方はRoundingModeを用いて指定した方が良いかと。
Qiita -【Java】BigDecimalをちゃんと使う~2018~

また、定数はfinal指定した方が読みやすいですよ。

コメントを受けて

しかし、ソースの[x][j]に引数3.0を指定すると、誤差の表示がループします。

x = x.subtract((x.multiply(x).subtract(j)).divide((j.multiply(x))));
『指数の2』と『定数項の-2』では絶対値は同じでも役割が全く異なります。
数値が同じだからと言って、双方を同じjにしていることが問題です。

書いてみた

なんとなく無理矢理感の漂うコードではありますが。

import java.math.BigDecimal;
import java.math.RoundingMode;

class Newton {
    //
    static final BigDecimal epsilon = BigDecimal.valueOf(1.E-16);
    static boolean isNearlyZero(BigDecimal num) {
        return epsilon.compareTo(num.abs()) > 0;
    }

    //
    final BigDecimal n;
    BigDecimal func(BigDecimal x) {
        // x^2 - n
        return x.pow(2).subtract(n);
    }
    static BigDecimal devFunc(BigDecimal x) {
        // 2x
        return BigDecimal.valueOf(2).multiply(x);
    }

    //
    Newton(int n) {
        this.n = BigDecimal.valueOf(n);

        BigDecimal x     = BigDecimal.valueOf(2.);
        BigDecimal delta = BigDecimal.valueOf(1.);

        for(int count = 1; !isNearlyZero(delta); ++count) {
            // x <- x - f(x) / f'(x)
            x = x.subtract(
                func(x).divide(devFunc(x), RoundingMode.HALF_UP)
            );
            delta = func(x);

            System.out.println(count + "回目 誤差=" + delta);
        }

        System.out.println("x = " + x);
        System.out.println("x * x = " + x.pow(2));
    }

    public static void main(String[] args) {
        try {
            new Newton(Integer.parseInt(args[0]));
        }
        catch(ArrayIndexOutOfBoundsException | NumberFormatException e) {
            System.err.println("USAGE: java Newton n");
            System.err.println("to compute sqrt(n)");
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/23 14:11

    回答ありがとうございました。
    数値を丸めることで、エラーは解消されました。

    追記がありますので、お時間が許せばそちらの方にも回答していただけると幸いです。

    キャンセル

  • 2018/03/23 14:32 編集

    そのまま適用したら導関数がプログラム上で3xになりませんか?
    変数jをどこで用いるべきか再考した方が良いかと。

    キャンセル

  • 2018/03/23 16:09

    回答ありがとうございました。
    大変な勘違いをしていました。変数を2で実行していたからたまたま通っていたんですね・・・。
    参考にさせていただきます!ありがとうございました!

    キャンセル

  • 2018/03/23 16:39 編集

    私もニュートン法について深めることができてよかったです!!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る