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

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

新規登録して質問してみよう
ただいま回答率
85.46%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

Q&A

解決済

3回答

1309閲覧

格子点の数から円周率を計算したい

PC_breakman

総合スコア30

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

0グッド

0クリップ

投稿2020/05/30 05:57

質問内容

半径の値(正の整数値)を入力したときに原点を中心とする入力した半径の円の中に含まれる格子点の数を数えて円周率を計算するプログラムを作成したのですが、n = 50000あたりからint型の範囲を超えてしまうのか、全く違う値が出力されてしまいます。どのようにすればこれを防ぐことができるのでしょうか。
ぜひ教えていただければと思います。

発生している問題・エラーメッセージ

自分の入力、出力結果 50000 50000:-2.333878859149978 正解の入力、出力結果 50000 50000:3.141592418000000

該当のソースコード

C

1#include <stdio.h> 2#include <math.h> 3 4double CountPoints(int N){ /*格子点の数を計算し円周率を計算する関数*/ 5 int x; 6 long Cp = 0; /*格子点の数*/ 7 double pi; /*円周率*/ 8 for(x = 1; x < N; x++){ 9 if(N * N - x * x >= 0){ /*円周で囲まれる部分の格子点を全てたす*/ 10 Cp += ((long) sqrt(N * N - x * x)); 11 } 12 } 13 Cp = ((long) 4 * Cp + 4 * N + 1); /*他の象限の扇形+軸の格子点+原点*/ 14 pi = ((double) Cp / (double) (N * N)); /* 面積/半径の二乗 */ 15 return pi; 16} 17 18int main(void){ 19 int n; 20 double Pi; 21 scanf("%d",&n); /*半径を入力*/ 22 Pi = CountPoints(n); /*入力された半径に応じて円周率を計算*/ 23 printf("%d:%.15f\n",n,Pi); /*半径と円周率を出力*/ 24 return 0; 25}

試したこと

for文の二重ループを使わず、各xの値について格子点がいくつ含まれるかを数え上げるようにしました。

補足情報

long型は使用できる環境です。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

C

1#include <stdio.h> 2#include <math.h> 3 4double CountPoints(int N){ /*格子点の数を計算し円周率を計算する関数*/ 5 int x; 6 long Cp = 0; /*「内部の」格子点の数*/ 7 long Cp_outside = 0;/*「円周上、頂点の」格子点の数*/ 8 double pi; 9 Cp_outside += N + 1; 10 for(x = 1; x < N; x++){ 11 Cp += ((long) sqrt((long long)N * N - (long long)x * x)) - 1; 12 Cp_outside += 2; 13 } 14 Cp_outside++; 15 Cp = ((long) 4 * (Cp_outside/2 + Cp -1)); /*他の象限の扇形+軸の格子点+原点*/ 16 return pi = ((double)Cp / ((double)N * N)); /*面積/半径の二乗 */ 17} 18 19int main(void){ 20 int n; 21 double Pi; 22 scanf("%d",&n); /*半径を入力*/ 23 Pi = CountPoints(n); /*入力された半径に応じて円周率を計算*/ 24 printf("%d:%.15lf\n",n,Pi); /*半径と円周率を出力*/ 25 return 0; 26}

これだとうまくいきましたよ。
11行目のNN、xx
16行目のpi=の後、N*N
このすべてをキャストしましょう。
16行目は(long long)N * Nでも構いません。

格子点の数え方も少しだけ修正しています。小さな半径では誤差かもしれないですが、大きくなると差が出てくるかもです。

投稿2020/05/30 07:29

XionCode

総合スコア46

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

PC_breakman

2020/05/30 08:46

回答ありがとうございます。少し考えてみます。
XionCode

2020/05/30 09:03

お互い頑張りましょう!
guest

0

ベストアンサー

n = 50000あたりからint型の範囲を超えてしまうのか、

C言語なので環境依存かと思いますが、手元の環境では、もう少し手前で N*N が 32bitを越えます。(正確には、符号付きなので、31bit)
Visual Studio 2019の CLコンパイラでは、long long とすることでは、もう少し広い範囲まで計算できるようです。(64bit)

オーバーフローと言う事に気付いているならば、途中計算の結果にも注意しましょう。

--- long long としてみた。[追記]

C

1double CountPoints(long long N){ /*格子点の数を計算し円周率を計算する関数*/ 2 long long x; 3 long long Cp = 0; /*格子点の数*/ 4 double pi; /*円周率*/ 5 for(x = 1; x < N; x++){ 6 if(N * N - x * x >= 0){ /*円周で囲まれる部分の格子点を全てたす*/ 7 Cp += ((long long) sqrt(N * N - x * x)); 8 } 9 } 10 Cp = ((long) 4 * Cp + 4 * N + 1); /*他の象限の扇形+軸の格子点+原点*/ 11 pi = ((double) Cp / (double) (N * N)); /* 面積/半径の二乗 */ 12 return pi; 13} 14 15int main(void){ 16 int n; 17 double Pi; 18 scanf("%d",&n); /*半径を入力*/ 19 Pi = CountPoints(n); /*入力された半径に応じて円周率を計算*/ 20 printf("%d:%.15f\n",n,Pi); /*半径と円周率を出力*/ 21 return 0; 22}

結果:
300000:3.141592641433333

投稿2020/05/30 06:20

編集2020/05/30 07:37
pepperleaf

総合スコア6383

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

PC_breakman

2020/05/30 06:53

回答ありがとうございます。 ご指摘の通りコードのlongをすべてlong longに変えたのですが、出力結果は同じでした。 これは計算そのものに問題があるということですか?
pepperleaf

2020/05/30 07:31

手元の環境(Window10, Visual Studio2019)では、一桁上まではOK。(それ以上は未確認) ただし、途中経過てのオーバーフローもあるので、注意が必要です。
PC_breakman

2020/05/30 07:46

かなり計算できるようになったのですが、n=100,000,000のときに違う値になってしまいました。これも原因はオーバーフローでしょうか。
pepperleaf

2020/05/30 07:53

> これも原因はオーバーフローでしょうか。 たぶん、、、。 最初の途中経過を 16進数で出力してみましたが、40000 で、8桁の最上位が、8を越えていました。(32bit符号付では負数なのでオーバーフロー) 64bitだと、、、すぐに出てこないですが、危なそう。2の 63乗を計算してください。
PC_breakman

2020/05/30 08:01

あなたのプログラムの標準出力 100000000:3.141592653586804 正解の標準出力 100000000:3.141592653586796 1億を入力した結果です。一番下の三桁だけが正解と違う値になります。 2の63乗は9.22*10^18でした。
pepperleaf

2020/05/30 08:06

計算精度については、きちんと検証していません。あしからず。
guest

0

xとNは整数のままとして、その他をdoubleで計算するようにしました。

C

1double CountPoints(int N) { /*格子点の数を計算し円周率を計算する関数*/ 2 int x; 3 long long Cp = 0; /*格子点の数*/ 4 double pi; /*円周率*/ 5 for (x = 1; x < N; x++) { 6 Cp += ((long)sqrt((double)N * N - (double)x * x)); 7 } 8 Cp = ((long)4 * Cp + 4 * N + 1); /*他の象限の扇形+軸の格子点+原点*/ 9 pi = ((double)Cp / ((double)N * N)); /* 面積/半径の二乗 */ 10 return pi; 11}

投稿2020/05/30 06:12

編集2020/06/01 03:35
etsuhisa

総合スコア416

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

etsuhisa

2020/05/30 06:28

すみません、質問内容を読み間違えました。
PC_breakman

2020/05/30 06:48

回答ありがとうございます。 それだと格子点以外も含まれませんか? 格子点のみを数え上げるように実装したいです。
etsuhisa

2020/05/30 07:16

sqrtからlongを削除したのはミスです。あと、x軸上の格子点を考慮するのを忘れていました。該当のソースを書き換えました。Cpはdoubleにしておかないと格子点数がオーバーしてしまうので。
etsuhisa

2020/05/30 07:21

格子点以外については、sqrtで算出した高さをそのxでの格子点数とする計算にしていると思いますので、元々格子点以外が含まれない計算方式だと思っています。
PC_breakman

2020/05/30 08:47

ありがとうございます。少し考えます。
etsuhisa

2020/05/30 09:14

他の方のを参考にしてください。後の計算で4倍しているので+1は間違いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問