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

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

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

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

Q&A

解決済

3回答

6680閲覧

桁数の大きな計算ができずに困っています

kaeruarc

総合スコア12

C

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

0グッド

1クリップ

投稿2018/01/16 12:48

###前提・実現したいこと
c言語で円周率を出すためのプログラムを作成しています。

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

コマンドライン引数の値から円周率出す(=値が大きいいほど円周率に近くなる)のですが200000ほどの数になると計算せずに停止してしまいます。

Segmentation fault: 11

###該当のソースコード

c言語

1/*これは円周率を出すプログラムですF2*/ 2 3#include <stdio.h> 4#include <stdlib.h> 5#include <math.h> 6 7double woris(double i); 8 9int main(int argc, char *argv[]) { 10 double a = atof(argv[1]); 11 printf("%.3f\n",woris(a)*2); 12 return 0; 13} 14 15double woris(double i){ 16 double answer = 0.0; 17 double dumper = i; 18 while(i>(double)0){ 19 if(dumper == (double)0){ 20 break; 21 }else if((int)i == 1){ 22 answer = (4*i*i)/(4*i*i-1); 23 return answer; 24 break; 25 }else{ 26 dumper = dumper -1; 27 answer = (4*i*i)/(4*i*i-1) * woris(dumper); 28 return answer; 29 } 30 } 31 return answer; 32}

###試したこと
float型だったものをdouble型にするなどをして徐々に桁の大きいものを計算することができるようになりました。しかし、

###補足情報(言語/FW/ツール等のバージョンなど)
macでxcodeをつかって入れたgccです。

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

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

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

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

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

guest

回答3

0

ベストアンサー

こんにちは。

再帰定義を使っているようですね。
再帰定義は再帰呼び出しする度にスタックを消費します。
最大スタック・サイズを増やすことは可能ですが、いくら増やしても無限にはふやせませんのでいつかスタックオーバーフローします。
深い再帰呼び出しをするケースでは、再帰定義ではなくループで処理するのが好ましいです。

投稿2018/01/16 12:58

Chironian

総合スコア23272

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

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

kaeruarc

2018/01/16 14:22

回答ありがとうございます。再帰ではなくforループを使うことで、無事に自分の手で問題を解決することのできるコードを書くことができました。
guest

0

woris関数内でworis関数を呼び出す再帰関数になっていますが、引数が200000であれば呼出しも200000回になるため、スタックオーバフローが発生していると思われます。解決する方法は二つです。

  1. 再帰呼び出しを止める。

woris関数内でworis関数を再帰的に呼び出さないように書き換えてください。

  1. 末尾再帰にする。

末尾再帰になるようにコードを修正し、十分な最適化オプションを付ければ、スタックを消費しないループの形に組み替えてコンパイルしてくれます。大きな数であってもスタックオーバーフローは発生しません。

末尾再帰に組み替えた場合の例を載せておきます。同時に無駄そうな処理も無くしています。

C

1#include <stdio.h> 2#include <stdlib.h> 3 4double woris_tail(double i, double pre); 5double woris(double i); 6 7int main(int argc, char *argv[]) 8{ 9 double a = atof(argv[1]); 10 printf("%.3f\n", woris(a) * 2); 11 return 0; 12} 13 14double woris_tail(double i, double pre) 15{ 16 if (i <= 0) { 17 return 0.0; 18 } 19 if ((int)i == 1) { 20 return (4 * i * i) / (4 * i * i - 1) * pre; 21 } 22 return woris_tail(i - 1, (4 * i * i) / (4 * i * i - 1) * pre); 23} 24 25double woris(double i) { return woris_tail(i, 1.0); }

コンパイラは賢いため、他のパターンでも末尾再帰とみなされる場合があります。ただ、確実に末尾再帰の最適化が反映されるようにしたい場合は、最後のreturn文がその関数の呼出しのみになるような形にしてください。

なお、末尾再帰最適化はC言語の必須要件ではありません。最適化を無効にしている場合、十分な最適化ができないコンパイラを使っている場合等は、末尾再帰にしてもスタックが消費させる形でコンパイルされますので、ご注意ください。GCCであれば-O2以上の最適化をすれば問題ありません。

投稿2018/01/16 13:25

編集2018/01/16 13:28
raccy

総合スコア21735

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

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

kaeruarc

2018/01/16 14:21

回答ありがとうございます。末尾再帰最適化といったことを知ることができて非常に参考になりました。
guest

0

オリジナルは無駄が多くていろいろ酷い

C

1double woris(double i) { 2 double answer = 0.0; 3 double dumper = i; 4 // (double)0は0.0か単に0と書くべき 5 while (i>(double)0) { 6 // 下のif文にはelse節があるので必ずどれかに一致するが 7 // どこに一致してもbreakかreturnするのでそもそもループになっていない 8 // つまりiがゼロより大きいときに一回実行されるだけ 9 10 // 最大で一回しか実行されない以上、下のdumperは必ずiと同じになる 11 // i>0なんだからdumper>0に決まっているので下のif文は無意味 12 if (dumper == (double)0) { 13 break; 14 } else if ((int)i == 1) { 15 // わざわざanswerに代入する必要もなさそう 16 answer = (4 * i*i) / (4 * i*i - 1); 17 return answer; 18 // 既にreturnして関数の外なので下のbreakは無意味 19 break; 20 } else { 21 // わざわざdumperやanswerに代入する必要もなさそう 22 // doubleなので1ではなく1.0 23 dumper = dumper - 1; 24 // dumper=iなのだから、woris(dumper)はworis(i - 1.0)でいい 25 answer = (4 * i*i) / (4 * i*i - 1) * woris(dumper); 26 return answer; 27 } 28 } 29 // ここにくるときはwhileの中は実行されていないのだからanswerは必ず0.0 30 // return 0.0でいい 31 return answer; 32 // ここまで読んでみると変数のanswerとdumperはなくてもいい 33}

簡潔に書くと

C

1double woris(double i) { 2 if (i > 0.0) { 3 if ((int)i == 1) { 4 return (4 * i*i) / (4 * i*i - 1); 5 } else { 6 return (4 * i*i) / (4 * i*i - 1) * woris(i - 1.0); 7 } 8 } 9 return 0.0; 10}

上のコードにすればループに直すのは簡単
引数のiはintでも良さそう

C

1double woris(int i) { 2 double pi, d; 3 if (i < 1) return 0.0; // エラー 4 for (pi = 1.0; i >= 1; --i) { 5 d = (double)i; 6 pi *= (4.0 * d * d) / (4.0 * d * d - 1.0); 7 } 8 return pi; 9}

つまり分数の掛け算を繰り返しているのならば
掛け算一回ごとにループさせればいいのです

解決したようなので返事はいりません^^

投稿2018/01/16 16:10

編集2018/01/16 16:39
colonq

総合スコア88

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問