【C言語】関数電卓の必要な計算は”数学関数”を活用せよ

C言語

ようこそ、KiRiOのブログへ!

本記事では「新・明解C言語中級編」の自由課題について解説します。


自由課題のとおり、この参考書には解答が載っておりません。
そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にしてみてください。

今回解説する課題は「演習1-5」で、課題内容は以下のとおりです。

演習1-5当てさせる数の範囲を事前に決定するのではなく、プログラム実行時に乱数で決定する《数当てゲーム》を作成せよ。
たとえば、生成して得られた二つの乱数が23と8,124であれば、23以上8,124以下の数を当てさせるようにする。
なお、プレーヤが入力できる最大の回数は、当てさせる数の範囲に応じて適切な値に自動的に(プログラム内での計算によって)設定すること。

今回の課題内容を整理しますと、以下のようになります。

  1. 整数範囲の自動指定
  2. 最大入力回数(試行回数)の自動計算

それでは、これらを順番に設計していきます。

設計

当てさせる数の範囲の自動指定

まずは《数当てゲーム》における範囲の指定です。
これまでの課題(演習1-3, 1-4)では、当てさせる数の範囲が課題の中で指示されていましたが、本問ではその範囲を乱数によって決定することが求められています。

具体的には、範囲の最小値最大値を乱数を生成することによって決めます。
そのため、乱数を2回生成させ、それぞれの値を変数として宣言した最小値と最大値に代入する必要があります。

ここで注意しなければならないことは、得られた2つの乱数の大小がわからないということです。
必ずしも、1回目の乱数が2回目の乱数より小さいとは限りません。
逆の場合もありますし、まったく同じ値が生成される可能性もあります。

そこで、まずは乱数を2回生成させ、それぞれの値を乱数用の変数に代入します。
次に、両者が異なる値であれば2つの変数の大小比較を行い、小さいほうを最小値用の変数に、大きいほうを最大値用の変数に格納します。

  1. 異なる2つの値が得られるまで乱数を2回生成し、乱数用の変数に代入
  2. 2つの乱数の代償を比較し、最小値用・最大値用の変数に格納

試行回数の自動計算

続いて、試行回数の自動計算です。
連続する $N$ 個の整数の中から1つの数を当てるために必要な試行回数 $x$ は以下の関係式より求まります。

$$x – 1 < \log_{2}N < x$$

詳細は下記ブログをご覧ください▼

【C言語】数当てゲームの入力回数は”指数不等式”で設定せよ
ようこそ、KiRiOのブログへ! 本記事では「新・明解C言語中級編」の自由課題について解説します。 自由課題のとおり、この参考書には解答が載っておりません。 そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にしてみ...

これまでは、上式から関数電卓などを用いて求めていましたが、今回はその計算をプログラム内で行わなければなりません。
四則演算に関しては容易に記述できますが、対数計算などはどのように処理すればよいのでしょうか。

この問題の解決策の一つが、C言語にあらかじめ用意されている数学関数を用いることです。
C言語には技術計算をサポートするための数学関数が用意されています。
これらを利用することで、指数・対数計算、三角関数などの計算を実行することができます。
本問における試行回数の算出過程は以下のとおりです。

  1. 最小値 $a$ 〜 最大値 $b$ までの整数の数 $N$ を求める:$N = b – a + 1$
  2. $N$ を真数とする、底が2の対数をとる:$\log_{2}N$
  3. $\log_{2}N$の切り上げ値を試行回数とする

この中の「2. 対数計算」「3. 値の切り上げ」で数学関数を用いることになります(具体的な記述方法については実装部分で説明します)。
設計は以上となります。

実装

それでは、設計内容をもとに以下の順番で実装していきます。

  1. 整数範囲の自動指定
  2. 試行回数の自動計算

整数範囲の自動指定

整数範囲を乱数で指定するためには、範囲の最小値と最大値を決める必要があります。
そのため「《数当てゲーム》を開始する」を選択した後に、まずはsrand()によって乱数の種を設定します。
その後、rand()を2回実行して乱数を生成します。
しかし、両者が同じだと範囲が決められませんので、異なる2つの乱数が生成されるまで処理を繰り返します
よって、ここではdo-while文を用いて記述します。

また、生成する乱数についてですが、単純にrand()のみで実行すると「0〜RAND_MAX」の範囲内で乱数が生成されます。
ただ、このRAND_MAXという値は処理系に依存し、少なくとも32,767以上の値となっています。
もちろん、このままでもよいのですが「0〜32,767」の中での《数当てゲーム》というのは難易度が上がってしまいます。
そこで、今回は課題内容を参考にして「1〜10,000」の範囲で乱数を生成することにします。

その後両者の値を比較し、小さいほうを最小値、大きいほうを最大値として整数範囲を指定します。
最小値・最大値が決まると、当てさせる数も決めることができます。
以上を実装したものが下記プログラムです。

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

int main(void)
{
	int try;
	int ans;
	int rand1, rand2;
	int min, max;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	srand(time(NULL));

	/* 異なる2つの乱数を生成 */
	do
	{
		rand1 = 1 + rand() % 10000;
		rand2 = 1 + rand() % 10000;
	} while (rand1 == rand2);

	/* 小さいほうを最小値、大きいほうを最大値とする */
	if (rand1 < rand2)
	{
		min = rand1;
		max = rand2;
	}
	else
	{
		min = rand2;
		max = rand1;
	}

	/* 当てさせる数の設定 */
	ans = min + rand() % (max - min + 1);

	return 0;
}

試行回数の自動計算

設計部分でも述べましたとおり、ここでは「対数計算」「値の切り上げ」を中心とした試行回数の算出方法について説明します。
まず前提として、C言語で用意されている数学関数を扱うためには、それらが宣言されているヘッダファイル「math.h」をインクルードする必要があります。
そして1つ目の対数計算ですが、底を2とする対数の計算には「log2()」を使います(そのままですね)。

log2
ヘッダ #include <math.h>
形式 double log2(double x);
機能 底を2としたxの対数を計算する。
戻り値 底を2としたxの対数の値。
x < 0 の場合は定義域エラー、x = 0 の場合は値域エラーとなる可能性がある。

ご覧のとおり、log2()の戻り値はdouble型の浮動小数点定数となっています。
そのため、試行回数を算出するためには、この値を切り上げなければなりません

ここで行うのが2つ目の「値の切り上げ」です。整数値への値の切り上げには「ceil()」を使います。

ceil
ヘッダ #include <math.h>
形式 double ceil(double x);
機能 x以上の最小の整数値を計算する。
戻り値 x以上の最小の整数値。

ここで注意しなければいけない点が、ceil()の戻り値もdouble型となっていることです。
そのため、仮にlog2()の値が「10.96」の場合、ceil(10.96)の戻り値は「11.00」のようになってしまいます。
そこで、ceil()の戻り値をint型でキャストする必要があります。

以上を実装したものが下記プログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h> // math.hをインクルード

int main(void)
{
	int try;
	int ans;
	int rand1, rand2;
	int min, max;
	int input_max;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	srand(time(NULL));

	do
	{
		rand1 = 1 + rand() % 10000;
		rand2 = 1 + rand() % 10000;
	} while (rand1 == rand2);

	if (rand1 < rand2)
	{
		min = rand1;
		max = rand2;
	}
	else
	{
		min = rand2;
		max = rand1;
	}

	ans = min + rand() % (max - min + 1);
	/* 指定した範囲をもとに試行回数を設定 */
	input_max = (int)ceil(log2(max - min + 1));

	return 0;
}

以降の「プレーヤによる数値入力」や「入力数値と正解との大小比較」については、過去のブログで解説していますので説明は省略します。
以上、すべてを実装したものが下記プログラムです。

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

int main(void)
{
	int try;
	int ans;
	int rand1, rand2;
	int min, max;
	int input_max, input_remain;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	srand(time(NULL));

	do
	{
		rand1 = 1 + rand() % 10000;
		rand2 = 1 + rand() % 10000;
	} while (rand1 == rand2);

	if (rand1 < rand2)
	{
		min = rand1;
		max = rand2;
	}
	else
	{
		min = rand2;
		max = rand1;
	}

	ans = min + rand() % (max - min + 1);
	input_max = (int)ceil(log2(max - min + 1));
	input_remain = input_max;

	printf("\n%d〜%dの数を当ててください(制限回数:%d回\n", min, max, input_max);

	do
	{
		printf("%2d回目:", (input_max - input_remain) + 1");
		scanf("%d", &num);

		input_remain--;

		if (input_remain == 0)
		{
			break;
		}
		
		if (num > ans)
		{
			printf("\a%3dよりも小さいです(残り%d回)\n", num, input_remain);
		}
		if (num < ans)
		{
			printf("\a%3dよりも大きいです(残り%d回)\n", num, input_remain);
		}
	} while (num != ans);

	if (num == ans)
	{
		printf("正解です!(入力回数:%d回)\n", input_max - input_remain);
	}
	if (num < ans)
	{
		printf("残念!正解は%dでした。\n", ans);
	}

	return 0;
}

以上で完了となります。
プログラムを実行して、範囲と試行回数が自動計算された《数当てゲーム》として機能しているか確認してみてください。

まとめ

それでは、本記事で説明した内容を以下にまとめます。

  1. C言語には技術計算をサポートするための数学関数が用意されている
    • 指数計算・対数計算・三角関数などさまざまな計算処理が可能
    • 利用する際はヘッダファイル「math.h」をインクルード
  2. 底を2とした対数の計算:log2()
    • 形式:double log2(double x)
    • xは真数のため正の数でなければならない
  3. 実数値の整数値への値の切り上げ:ceil()
    • 形式:double ceil(double x)
    • 戻り値がdouble型のためint型にキャストする必要がある

C言語に用意されている数学関数は他にもたくさんあります。こちらからご覧ください▼

C言語の標準ライブラリ一覧(ヘッダ毎)
C言語の標準ライブラリ一覧(ヘッダ毎)です。

ここまで、記事を読んでいただき、ありがとうございました。

最後に、今回取り上げた課題が記載されている本を紹介します。
今回扱った課題以外にも、様々なプログラム開発について記載されているため、この記事とあわせて読んでいただくと、より理解が深まると思います。


また、参考として以下の本もご紹介しておきます。

こちらもオススメです。2021年12月14日に最新版が発売されました!

それでは、また別の記事でお会いしましょう!

コメント

タイトルとURLをコピーしました