まず、StackOverflow へようこそ! プログラミングを学ぶ上で非常に貴重なリソースになると確信しています。とはいえ、サイトを最大限に活用するには、サイトのガイドラインに従うことが重要です。一般に、SO はフォーラムではなく Q&A サイトであることを覚えておいてください。
一般的な問題
投稿したコードにはいくつかの問題があります。
A. ホワイトスペース
空白を使用すると、他のプログラマーと自分自身の両方が コードを読みやすく理解しやすくするのに非常に役立ちます。スコープを示すためのタブ移動(書き込み
bool condition = ...
if (condition) {
action();
}
それよりも
bool condition = ...
if (condition) {
action()
}
これはアクションのスコープを示していません) 特に、投稿したコードのように複数のネストされた スコープがある場合、読みやすさが劇的に向上します。 間隔も重要です。
B. 使用cout
1.
書くのではなく、cout << "" << endl
単に書くことができますcout << endl
。
2.
と の間には違いがendl
あり"\n"
、コードからわかるように、それらを認識している可能性があります。この 2 つを使用した場合のパフォーマンス特性は同一ではありませんが、この段階では最適化が時期尚早であることを考慮すると. endl
この種のロギングに固執する方が読みやすいです。それよりも
cout << "All cycle #1 approximate square are guessed\n" << "using 1 as the approximate." << endl;
書いたほうがいいでしょう
cout << "All cycle #1 approximate square are guessed" << endl << "using 1 as the approximate." << endl;
このコンテキストで。
3.
1 行のコードに無理なくintを挿入する 場合cout
(このコードは、
cout << "This line of code is longer than feels comfortable." << endl << "It would be much more pleasant to break this into several lines so that I don't need to scroll to the right to view the whole thing." << endl;
たとえば、1 行では長すぎます)、コードのように、
cout << "Anmol's Square Root Calculator!" << endl;
cout << endl;
cout << "This program will compute the square root\n";
cout << "of a number using the Babylonian algorithm!\n";
cout << endl;
cout << "Only positive numbers work with this algorithm!" << endl;
cout << endl;
cout << "All cycle #1 approximate square are guessed\n" << "using 1 as the approximate." << endl;
cout << endl;
cout << endl;
cout << endl;
挿入演算子をcout
複数回使用すると、次のように書くことができます
cout << "Anmol's Square Root Calculator!" << endl
<< endl
<< "This program will compute the square root" << endl
<< "of a number using the Babylonian algorithm!" << endl
<< endl
<< "Only positive numbers work with this algorithm!" << endl
<< endl
<< "All cycle #1 approximate square are guessed" << endl
<< "using 1 as the approximate." << endl
<< endl
<< endl
<< endl;
C. モジュール性
すべてのコードはmain()
. この短いプログラムでも、読みやすさは損なわれます。さらに重要なことは、このプログラムで作成した機能を他の場所で再利用できないことです。
適切なアプローチは、プログラムを個別のコンポーネントに分割することです。このプログラムでは、次の設定が行われています。
show a greeting
do {
get number from user
compute the squareroot of that number while logging
show the user the squareroot
ask the user if they want to do another computation
} while the user wants the program to keep running
show a farewell
この時点で、各ステップの関数を記述できます。ここで「書いている」関数は、あなたのコードから抜粋したもので、ごくわずかに変更されています。
画面への印刷
ユーザーに挨拶と別れを表示する関数は、(ほとんど) を使用するだけでよいため、最も簡単ですcout
。
void showGreeting()
{
cout << "Anmol's Square Root Calculator!" << endl
<< endl
<< "This program will compute the square root" << endl
<< "of a number using the Babylonian algorithm!" << endl
<< endl
<< "Only positive numbers work with this algorithm!" << endl
<< endl
<< "All cycle #1 approximate square are guessed" << endl
<< "using 1 as the approximate." << endl
<< endl
<< endl
<< endl;
}
void showFarewell()
{
cout << "Thank you for using a program made by Anmol Sethi!" << endl
<< endl
<< endl
<< endl;
cin.get();
}
ユーザー入力の取得
次に、ユーザーから数値を取得する関数が必要です
double getInputNumber()
{
double num = 0.0f;
cout << "Please enter the number you would like to compute the square root of: ";
cin >> num;
cout << endl;
do {
if (num <= 0)
{
cout << endl
<< "Invalid input. Please re-enter a valid number: ";
cin >> num;
cout << endl;
}
} while (num <= 0);
cout << endl
<< endl
<< endl;
return num;
}
さらに、ユーザーがプログラムの再実行を希望しているかどうかを判断する必要があります。プログラムの観点 ( の観点main
) から見ると、これは非常にブール的な質問です。何らかの関数を呼び出す必要があり、それはtrue
orを返す必要がありfalse
ます。ただし、関数内ではユーザーと対話しているため、'y'
andを使用するの'n'
が理想的です (少なくとも英語を話すユーザーにとっては)。
bool getRunAgain()
{
bool choice = false;
char playAgain = 'n';
do {
cout << endl
<< "Would you like to calculate again? (y/n): ";
cin >> playAgain;
cout << endl;
do {
cout << endl;
if ((playAgain !='y' && playAgain !='n'))
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
} while (playAgain !='y' && playAgain !='n');
} while (playAgain !='y' && playAgain !='n');
if (playAgain == 'y') {
choice = true;
} else /*if (playAgain == 'n')*/ {//At this, playAgain is either 'y' or 'n'. So if it's not 'y', it's 'n'.
choice = false;
}
return choice;
}
このコードを関数として書き直したおかげで、ここで必要以上の作業が行われていることが簡単にわかります。外側のdo...while
ループを考えてみましょう。その繰り返しの条件はplayAgain !='y' && playAgain !='n'
. 一見すると、これは理にかなっているように見えます。ユーザーが「はい」または「いいえ」のいずれかを入力したことを確認する必要があります。do...while
しかし、まったく同じ条件の内部ループがあることに注意してください。つまり、 がまたはplayAgain
と等しくない限り、内側のループは終了しません。したがって、内側のループを終了するまでに、がまたはであることを確認できます。したがって、再度確認する必要はありません。これにより、関数を次のように書き直すことができます。'y'
'n'
playAgain
'y'
'n'
bool getRunAgain()
{
bool choice = false;
char playAgain = 'n';
cout << endl
<< "Would you like to calculate again? (y/n): ";
cin >> playAgain;
cout << endl;
do {
cout << endl;
if ((playAgain !='y' && playAgain !='n'))
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
} while (playAgain !='y' && playAgain !='n');
if (playAgain == 'y') {
choice = true;
} else /*if (playAgain == 'n')*/ {
choice = false;
}
return choice;
}
しかし、残りのループ内にも同様の状況があります。
do {
cout << endl;
if ((playAgain !='y' && playAgain !='n'))
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
} while (playAgain !='y' && playAgain !='n');
あまり重要ではない を除いて、cout << endl
でない限りループに入りたくないようです(playAgain !='y' && playAgain !='n')
。これは、while
ループではなくループを呼び出しdo...while
ます。
while (playAgain !='y' && playAgain !='n')
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
これを関数にドロップすると、次のようになります
bool getRunAgain()
{
bool choice = false;
char playAgain = 'n';
cout << endl
<< "Would you like to calculate again? (y/n): ";
cin >> playAgain;
cout << endl;
while (playAgain !='y' && playAgain !='n')
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
if (playAgain == 'y') {
choice = true;
} else /*if (playAgain == 'n')*/ {
choice = false;
}
return choice;
}
平方根の計算
最後に、バビロニア アルゴリズムを使用して平方根を計算する関数が必要です。コードを取得するだけです
int count(25), cycle(1);
double guess, sqrt, num;
cout << "Please enter the number you would like to compute the square root of: ";
cin >> num;
cout << endl;
for (guess=1; count!=0; count--)
{
guess =(guess + (num/guess))/2;
cout<<""<<endl;
cout<<"Cycle "<<cycle<<" Aproximate: "<<guess<<endl;
sqrt = guess;
cycle++;
}
double を受け取り、1 を返す関数に入れます。
double computeSquareRootBabylonian(double num)
{
int count(25), cycle(1);
double guess, sqrt;
for (guess = 1; count != 0; count--)
{
guess = (guess + (num/guess))/2;
cout << endl
<< "Cycle " << cycle << " Aproximate: " << guess << endl;
sqrt = guess;
cycle++;
}
return sqrt;
}
ここで終了することもできますが、機能を改善するための変更がいくつかあります。
1.
初期近似 (使用している初期推定値1
) は、実際にはこの関数の一部ではありません。この関数を呼び出すコードは、その開始点を指定する必要があります。
double computeSquareRootBabylonian
(double num,
double initialApproximation)
{
double approximation, sqrt;
unsigned int count(25), cycle(1);
for (approximation = initialApproximation; count != 0; count--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << cycle << " Aproximate: " << approximation << endl;
sqrt = approximation;
cycle++;
}
return sqrt;
}
2.
反復回数 (または呼び出しているサイクル) も、実際には関数の一部ではありません。この関数を呼び出すコードは、その番号を指定する必要があります。
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations)
{
double approximation, sqrt;
unsigned int iterationsRemaining = iterations;
unsigned int iteration = 1;
for (approximation = initialApproximation; iterationsRemaining != 0; iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << iteration << " Aproximate: " << approximation << endl;
sqrt = approximation;
iteration++;
}
return sqrt;
}
3.
このfor
ループは奇妙です。「ループ変数」ではありませんが、初期化 で変数guess
(名前を変更しました)を初期化するため、奇妙です。コードの 2 番目のチャンクでは、approximation
while (count > 0)
{
guess =(guess + (num/guess))/2;
cout << endl;
cout << "Cycle " << cycle << " Aproximate: " << guess << endl;
sqrt = guess;
count-=1;
cycle+=1;
}
代わりは。このコードの意図ははるかに明確ですが、意図は標準的な方法for
でループを使用することで最も明確になります (ループの条件が依存し、各ループの最後に変更される初期化で同じ変数を初期化します: )。ここで繰り返されている変数は(私が と呼んだもの) であり、それが より大きい限り、ユーザーが指定した反復回数 (引数)から減少しています。この状況では、次の for ループが必要です。for (int i = 0; i < 10; i++)
count
iterationsRemaining
iterations
1
0
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
//...
}
approximation
this を関数に代入するときは、次のように初期化する必要がありますinitialApproximation
。
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations)
{
double approximation = initialApproximation;
unsigned int iteration = 1;
double sqrt;
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << iteration << " Aproximate: " << approximation << endl;
sqrt = approximation;
iteration++;
}
return sqrt;
}
余談: 時期尚早の最適化
あなたが書いた
本で、アルゴリズムに for ループを使用する方が効率的であると読んだので、while ループの代わりに for を使用したいと思います。
for
これは、ループを使用する正当な理由ではありません。実行速度が遅すぎることが確実でない限り、(当然のことながら) コードを変更して高速化する必要はありません。読んだときに意味のあるコードを書くことは、はるかに重要です。
4.
変数は何をしますsqrt
か?の値が繰り返しapproximation
代入され、関数から返されますが、返されたときの値は の値と同じですapproximation
。この理由でそれを削除すると、時期尚早の最適化になります。ただし、変数を呼び出すとsqrt
、近似ではなく真の平方根であることが示唆されます。このため、削除する必要があります。
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations)
{
double approximation = initialApproximation;
unsigned int iteration = 1;
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << iteration << " Aproximate: " << approximation << endl;
iteration++;
}
return approximation;
}
5.
各反復で逐次近似をログに記録することは、平方根関数で常に実行したい種類のものではありません。呼び出しコードが近似値をログに記録するかどうかを指定できるようにする引数を追加する必要があります。
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations,
bool log)
{
double approximation = initialApproximation;
unsigned int iteration = 1;
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
if (log)
{
cout << endl << "Cycle " << iteration << " Aproximate: " << approximation << endl;
}
iteration++;
}
return approximation;
}
メイン関数を書く
これで、プログラムのすべてのコンポーネントが配置されました。計画を変えるしかない
show a greeting
do {
get number from user
compute the squareroot of that number while logging
show the user the squareroot
ask the user if they want to do another computation
} while the user wants the program to keep running
show a farewell
完全なプログラムに:
int main(int argc, char *argv[])
{
//Do this stuff.
}
まず、プログラムの開始方法と終了方法を知っています。showGreeting()
関数andを使用して、挨拶と別れを示したいと思いますshowFarewell()
。
int main(int argc, char *argv[])
{
showGreeting();
//do {
//get number from user
//compute the squareroot of that number while logging
//show the user the squareroot
//ask the user if they want to do another computation
//} while the user wants the program to keep running
showFarewell();
}
ユーザー入力を取得して平方根を少なくとも 1 回計算する必要があることはわかっています。bool
また、ユーザーが関数 を使用して別の平方根を計算するかどうかを表すを取得できることもわかっていますgetRunAgain()
。do
ユーザー入力と計算が必要while
getRunAgain()
です。
int main(int argc, char *argv[])
{
showGreeting();
do {
//get number from user
//compute the squareroot of that number while logging
//show the user the squareroot
} while(getRunAgain());
showFarewell();
}
ユーザーが平方根を計算したい数値を表すgetInputNumber()
を返す関数があります。double
これを 2 回使用する必要があります。1double
回は平方根関数の引数として、もう 1 回computeSquareRootBabylonian()
は入力とその平方根をユーザーに出力するために使用します。その結果、ローカル変数が必要になります。
int main(int argc, char *argv[])
{
showGreeting();
do {
double number = getInputNumber();
//compute the squareroot of that number while logging
//show the user the squareroot
} while(getRunAgain());
showFarewell();
}
ここで、平方根関数computeSquareRootBabylonian()
は 4 つの引数を取ります。
- 平方根を計算する数
- 初期近似
- 反復回数
- 各逐次近似をログに記録するかどうか
number
最初の引数として渡します。今のところ、最初の概算、反復回数、およびログを記録するかどうかに使用して、残りの引数をハードコーディングできます。 1
25
true
computeSquareRootBabylonian()
(結果をログアウトするとき) 1 回の結果を使用するだけでよいため、ローカル変数を使用せずに回避できます。ただし、わかりやすくするために、次のいずれかを使用してみましょう。
int main(int argc, char *argv[])
{
showGreeting();
do {
double number = getInputNumber();
double squareRoot = computeSquareRootBabylonian(number, 1.0f, 25, true);
//show the user the squareroot
} while(getRunAgain());
showFarewell();
}
結果をユーザーに表示するだけです。
int main(int argc, char *argv[])
{
showGreeting();
do {
double number = getInputNumber();
double squareRoot = computeSquareRootBabylonian(number, 1.0f, 25, true);
cout << endl
<< "The square root of " << num << " is " << sqrt << "!" << endl
<< endl;
} while(getRunAgain());
showFarewell();
}
このプログラムで省略されていることが 1 つあります: の精度の設定ですcout
。
int main(int argc, char *argv[])
{
cout.precision(50);
showGreeting();
do {
double number = getInputNumber();
double squareRoot = computeSquareRootBabylonian(number, 1.0f, 25, true);
cout << endl
<< "The square root of " << num << " is " << sqrt << "!" << endl
<< endl;
} while(getRunAgain());
showFarewell();
}
質問を言い換える
についていくつか質問しましたcout
:
このコード [最初のmain
関数] では、10 進数の精度を具体的に 15 に設定しています。ただし、12 桁の数値を出力します。しかし、このコード [2 番目のmain
関数] では、小数点以下 15 桁で出力されます。[...] なぜこれが起こっているのか、私は非常に混乱しています。cout << fixed << showpoint; を試しました。しかし、num が理由もなく 15 個のゼロを出力すると不快に見えます。なぜこれが起こっているのか、それを修正する方法について誰かが教えてくれますか?
小数点以下 15 桁を超えることができないのはなぜですか? 精度を 30/40 に設定すると、コンピューターの計算機では 30/40 になりますが、出力には何も影響しません。double の代わりに使用する必要がある別の整数型はありますか?
これらの質問については後ほど説明します。最初に、StackOverflow でより適切かつ迅速な応答を得るために、私が彼らにどのように質問したかを説明します。
主なポイントは、短い自己完結型の正しい (コンパイル可能な) exampleを投稿することです。では、ここでの問題は正確には何ですか?それは、 (一般的に)に(を使用して)挿入することと関係があります。を使用して出力をフォーマットすることに関係しています。この時点まで問題を減らした後、関連するドキュメントを調べて、十分な説明が得られない場合は、これらのキーワードを使用して StackOverflow を検索します。問題の解決策が見つからない場合は、問題を説明 する例を生成します。operator <<
std::cout
std::ostream
double
std::ostream::precision
最初の質問にたどり着く
最初の問題は、cout の精度を 15 に設定すると、小数点以下 12 桁しか得られないことです。a を値に初期化し、 todouble
の精度を設定し、 ourを into に挿入するプログラムを書きましょう:cout
15
double
cout
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
double value = 1.1111111111111111111111111f;
cout.precision(15);
cout << value << endl;
return 0;
}
このプログラムは出力します
1.11111111111111
この数値1
の小数点以下は 14 だけです。これは私たちが望んでいたものではありませんが (小数点以下の桁数が 12 桁ではなく 14 桁になっています)、正しい軌道に乗っているようです。ある種の丸め動作が行われているようです。おそらく、小数点第 13 位と第 14 位にゼロを入れる必要があります。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
double value = 1.1111111111110011111111111f;
cout.precision(15);
cout << value << endl;
return 0;
}
このプログラムの出力
1.111111111111
期待どおり、12 桁しかありません。これで、短い自己完結型の正しい (コンパイル可能な) 例を質問に入れることができました。
最初の質問を述べる
std::cout
を出力するために使用していますdouble
。小数点以下の最初の 15 桁、または少なくとも 0 以外の桁数を表示したいと考えています。しかし、このコードでは、たとえば
int main(int argc, char *argv[])
{
double value = 1.1111111111110011111111111f;
cout.precision(15);
cout << value << endl;
return 0;
}
出力は私が期待したものではあり1.111111111111
ませ1.111111111111001
ん。を使用してこれに対処できましたstd::fixed
int main(int argc, char *argv[])
{
double value = 1.1111111111110011111111111f;
cout.precision(15);
cout << std::fixed << value << endl;
return 0;
}
期待どおりに出力1.111111111111001
します。残念ながら、 を使用すると、たとえすべての桁がゼロであっても、常に小数点以下 15 桁が出力されますstd::fixed
。たとえば、からの出力
int main(int argc, char *argv[])
{
double value = 1.0f;
cout.precision(15);
cout << std::fixed << value << endl;
return 0;
}
私が望んでいた よりも1.000000000000000
むしろです。1
cout
残りの桁がすべてゼロの場合、最大 15 桁以下の 10 進数を出力するにはどうすればよいですか?
2番目の質問を考え出す
2 番目の問題は、 の精度を 15 よりも高く設定cout
しても、出力で 10 進数の 15 桁しか得られないことです。a を値に初期化し、 todouble
の精度を設定し、 ourを into に挿入するプログラムを書きましょう:cout
30
double
cout
int main(int argc, char *argv[])
{
double value = 1.55f;
cout.precision(30);
cout << value << endl;
return 0;
}
これにより1.5499999523162841796875
、小数点以下 22 桁の数値が出力されます。これは、正確に 15 桁の出力を取得できなかったことがどういうわけか運が悪かったことを示唆しています。この丸め動作では、小数点以下 15 桁が出力されていました。
質問に答える
ゼロ以外の 10 進数が 15 桁まで表示されるように a を挿入します。s の文字列表現は、double
少なくとも次のテストを満たす必要があります。cout
double
cout
x.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxf ----> "x.xxxxxxxxxxxxxxx"
x.xxxxx0000000000000000000000000f ----> "x.xxxxx"
x.000000000000000000000000000000f ----> "x"
x.00000000000000x000000000000000f ----> "x.00000000000000x"
これは、 を使用して明白な方法で達成することはできませんstd::ostream::precision
。ご指摘std::fixed
のとおり、上記の 3 番目のテストに確実に失敗するため、使用できません。cplusplus.comで述べられているように、デフォルトを簡単に使用することもできません。
デフォルトの浮動小数点表記では、精度フィールドは、小数点の前と後の両方をカウントして合計で表示する意味のある最大桁数を指定します。
これは、小数点以下の桁数を考慮する必要があることを意味します。小数点以下が 3 桁の場合は、15+3 の精度を使用する必要があります。
double
これを実現する 1 つの方法は、 を受け取り、を返す関数ですstd::string
。
std::string stringFromDouble(double value, unsigned int decimalDigits)
{
int integerComponent = abs((value > 0) ? (int)(floor(value)) : (int)(ceil(value)));
std::ostringstream integerStream;
integerStream << integerComponent;
std::string integerString = integerStream.str();
unsigned int integerLength = integerString.length();
std::ostringstream stream;
stream.precision(decimalDigits + integerLength);
stream << value;
std::string str = stream.str();
return str;
}