weemiee
@weemiee (weemiee)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【C++】円周率の近似値が途中で発散してしまう

Q&A

Closed

次のサイト

で紹介されている円周率計算のプログラムについて質問です。

#include <iostream>
#include <math.h>
#include <stdio.h>
using namespace std;
int main()
{
	int n = 0;
	double temp = 0.0;
	cin >> n;
	for (int i = 1; i < n; i++) {
		if ((i % 2) == 1) {
			temp += (1.0 / (2 * i - 1));
		}
		else {
			temp -= (1.0 / (2 * i - 1));
		}
	}
	double ans = temp * 4.0;
	printf("%15.14lf", ans);
    return 0;
}

n:加算する分数の個数 (キーボード入力)
temp:分数を個数分加算して最終的な値を4倍にして (ans) 出力
i:ループのカウント

このプログラムを「Replit」にて実行した時、nを大きくしていく事で出力値ansはπに収束するかと思いきや、nをint型の上限値 (2147483647) まで近づけると、今度はansの振れ幅が大きくなり、πの理論値から遠のき始めました。
IdolizedMortifiedSolution - Replit および他 32 ページ - ユーザー 1 - Microsoft​ Edge 2023_04_19 13_16_24.png

次のサイトのプログラムだと、入力値を十分に大きくしても出力値は期待通りπに収束します。

#include <stdio.h>
#include <time.h>

int main()
{
	double limit = 2147483640;
	double l = 0.0;
	for (double n = 1; n <= limit; n += 4)
	{
		l += (1.0 / n) - (1.0 / (n + 2));
	}
	printf("%1.16f", l * 4);
	return 0;
}

Screenshot 2023_04_19 13_32_17.png

int型であるnumのとりうる値の範囲も考慮してキーボード入力したつもりなので、一番上のプログラムの加算方法でなぜ出力値が途中から発散し出すのか疑問でなりません。ささいな事でも良いので、アドバイス頂けないでしょうか?

0

3Answer

解析はまったくしていませんが、
(1.0 / (2 * i - 1))

(1.0 / (2.0 * i - 1.0))
にしたら精度は出ているように見えました。ご確認ください。

2Like

計算式中にある 2 * i の評価結果は int として扱われます.
したがって,これが int で表せる最大の値を超えてはいけません.

1Like

@imagouさん、@satomineさん、ご回答ありがとうございます。小数表記に変えたら、確かに正しく動きました。

ここで私の方でも、もう1種類プログラムを作ってみました。

#include <stdio.h>

int main(){
	double l = 0.0;
	for (int n = 1; n <= 2147483640; n++){
		l += (n % 2 == 1) ? (1.0 / (double)(n * 2 - 1)) : (-1.0 / (double)(n * 2 - 1));
	}
	printf("%1.16f", l*4.0);
	return 0;
}

このプログラムも結果は変わらず、やはり途中で発散します。
私の方でもよく調べてみたところ、次のサイトで説明されていた内容が気になりました。

代入前に(double)とつけることによって「この式はdouble型の計算ですよ!」とコンパイラに伝えることで、小数点以下の値が失われるのを防げます。

代入前に(double)とつける事で「この式はdouble型の計算ですよ!」とコンパイラに伝わるなら、(double)(n * 2 - 1)についても「(n * 2 - 1)」が予めdouble型として理解された上で、double型の演算方法によって打ち出されるのではないでしょうか?(→ n * 2 の値もdouble型として扱われる?)

とはいっても結果が期待通りでないので、どうした良いか分からず悩んでいます。

1Like

Comments

  1. 後は自分で考えて的な回答ですみません。
    いい気付きですね!あと一歩のところまで来たと思います!

    ご提示いただいた例ですと、そもそもn * 2がintとして評価されてしまうので、nが大きすぎるとオーバーフローします(@satomineさんの書かれている通り)。

    ので、最初にdouble型として計算させる必要があります。
    n * 2.0とするか、(double)n * 2で期待通りとなるかと。試してみてください。

  2. えらく簡易的なプログラムですが:

    #include <stdio.h>
    int main(void) {
        const int n = 2147483647;
        double d;
        
        d = (n * 2);
        printf("%f\n", d);
    
        d = (n * 2.0);
        printf("%f\n", d);
        
        d = ((double)n * 2);
        printf("%f\n", d);
    }
    

    結果:

    -2.000000
    4294967294.000000
    4294967294.000000
    
  3. @weemiee

    Questioner

    @imagouさん、アドバイス頂きありがとうございます。
    (double)(n * 2 - 1)だと、「(n * 2 - 1)」がdouble型と認識される時には既に「(n * 2 - 1)」の演算 (nは元々int型なので、int型としての計算になる) が行われているという事ですね。
    nの前に直に「double」をつける事は思いつかなかったため、とても助かりました。

  4. 補足的な説明です。
    四則演算では計算前に左右の型が同じに調整され、演算結果もその型になるルールです。
    左右のオペランドの両方が long double ではなくどちらかが double である場合には double でない側は double に型変換されてから計算されて結果も double であるということになっているのでオペランドの片側をキャストすれば十分なわけです。
    この場合でいえば n*2.0 と書くだけでも暗黙に n の側も double に変換されます。
    もちろん (double)n*2.0 と書いてもよいですが、煩雑なので習慣的には片側を double になるようにするだけで済ますことが多いですね。

  5. @weemiee

    Questioner

    @SaitoAtsushiさん、アドバイス頂きありがとうございます。
    double型は、異なる型同士を演算する際の型変換においてlong doubleよりも優先順位が高い、という事ですね。最近プログラムの中で、浮動小数点数を用いた四則演算を使用する機会が増えたため、非常に勉強になりました!

Your answer might help someone💌