前回の練習問題の解答例

#include <stdio.h>

int main(void)
{
	printf("合計金額は%d円です。\n",80*12+180*4);
	return 0;
}
数式は原則として左から順に進むが、加算より乗算が優先されるので、80*12+180*4 の計算は次のように進む。
まず 80*12 が計算され、次に 180*4 に進み、最後に 960+720 が計算される。
答は 1680 である。 この数値が「%d」の位置に当てはめられて「合計金額は1680円です。」と表示される。

実数

C言語では、小数点の付いた数のことを実数と呼ぶ(数学の定義とは異なる)。 整数と実数は扱い方が違う。 次のプログラムで違いを確認しよう。
#include <stdio.h>

int main(void)
{
	printf("4わる3は%dです。\n",4/3);
	printf("4.0わる3.0は%fです。\n",4.0/3.0);
	return 0;
}
「4/3」の答は1に、「4.0/3.0」の答は「1.333333」になるはずである。
整数と実数の扱いには次の3点の相違がある。
  1. printfで整数を表示する時に%dを使ったが、実数を表示する時は%fを使う
  2. 実数は切りの良い数値でも小数点を付ける
  3. 実数の除算は小数点以下まで計算される(実数の剰余を求める演算子はない)


整数の上限と下限

52枚のトランプから6枚を選ぶ時、選び方が何通りあるか計算する。 つまり、526=52!/(6!46!)の計算である。
#include <stdio.h>

int main(void)
{
	printf("52枚のトランプから6枚を選ぶ選び方は%d通り。\n",(52*51*50*49*48*47)/(6*5*4*3*2*1));
	return 0;
}
52!/(6!46!)を約分した式を使っている。 結果は2462822通りとなるだろう。 しかし、大きな桁に対応している電卓を使って計算すると(または手計算すると)20358520通りとなり、両者は食い違っている。 どちらが正しいのだろうか?
実は、C言語による計算が間違っている。 C言語で扱える整数は -21億4748万3648〜21億4748万3647 の範囲に限られる。 結果が範囲外になるような計算をした場合、答は正しい値にならず、範囲内のどれかの数値になってしまう。
上の式の場合、括弧( )の中が先に計算されるので最初に(52*51*50*49*48*47)が計算されるが、その答は14658134400(146億5813万4400)で範囲を超えている。 この時点で計算結果は間違った値になる。 すると、その後の計算も間違ったまま進んでしまう。 つまり、本来の最終的な答(上の例では 20358520)が範囲内であっても、計算の途中で範囲を超えると正しい答は得られないということである。

理解を深めるために、上限より1大きい数と、下限より1小さい数を計算してみよう。
#include <stdio.h>

int main(void)
{
	printf("2147483647に1を加えると%dになる。\n",2147483647+1);
	printf("-2147483648から1を引くと%dになる。\n",-2147483648-1);
	return 0;
}
計算結果を見ると、
2147483647+1=-2147483648
-2147483648-1=2147483647
と間違った答になっている(よく見ると、数値が循環していることが分かるだろう)。
なお、整数の範囲-21億4748万3648〜21億4748万3647は、要するに-231〜231-1ということである。C言語の整数は32ビットで表されるので、全部で232通りの数値を表すことができる。正数と負数に半分ずつ使うと231通りずつとなるが、0を表すのに1つ必要なので、上限は231より1小さくなっている。 ただし、システムによっては整数が16ビットで表されることもあり、その場合の整数の範囲は -215〜215-1 すなわち -32768〜32767 となる。

整数の限界を回避する方法

実数が扱える範囲は53ビットであり整数よりずっと広いので、実数で計算すれば正しい答が得られる。
(実数は、53ビットに加えて桁を表す11ビットも使い、合計64ビットで数値を表している。ただし、整数とは構造が異なるため数値の範囲が -252〜252-1 や -263〜263-1 となるわけではない。)

先ほどのプログラム中の数値を全て実数にしてみよう。
#include <stdio.h>

int main(void)
{
	printf("52枚のトランプから6枚を選ぶ選び方は%f通り。\n",(52.0*51.0*50.0*49.0*48.0*47.0)/(6.0*5.0*4.0*3.0*2.0*1.0));
	return 0;
}
計算結果は 20358520.000000 となり、正しい答が得られている。

さらに、このプログラム中の%f%.0fに書き換えると答が見やすくなる。
#include <stdio.h>

int main(void)
{
	printf("52枚のトランプから6枚を選ぶ選び方は%.0f通り。\n",(52.0*51.0*50.0*49.0*48.0*47.0)/(6.0*5.0*4.0*3.0*2.0*1.0));
	return 0;
}
計算結果が 20358520 と小数点以下のない形で表示される。
%fにドットと数値を追加することで、小数第何位まで表示するか指定することができる。 例えば、%.3fは小数第3位まで表示、%.0fは小数点以下を表示しない、という意味になる。

整数と実数が混ざった式

演算は整数同士あるいは実数同士で行なうものである。 整数と実数が混ざった式を書くこともできるが、その場合は整数と実数の計算が行なわれる時点で整数が実数に変換され、実数同士として計算される。 例えば、「1+2+5.0」は「1+2」→「3+5.0」という順に計算が進むが、2つ目の演算は3が3.0に変換されて「3.0+5.0」という実数同士の演算となり、答は実数8.0となる。

次のプログラムは、税抜80円のバナナを5本買った時の税込価格を計算している。
整数が実数に変換されるのは整数と実数の計算が行なわれる時点である。これを踏まえ、どの時点で整数が実数に変換されるかを考えながら、結果を見てみよう。
#include <stdio.h>

int main(void)
{
	printf("税抜80円のバナナを5本買うと税込%f円です。\n",108.0/100  *80  *5  );
	printf("税抜80円のバナナを5本買うと税込%f円です。\n",108  /100.0*80  *5  );
	printf("税抜80円のバナナを5本買うと税込%f円です。\n",108  /100  *80.0*5  );
	printf("税抜80円のバナナを5本買うと税込%f円です。\n",108  /100  *80  *5.0);
	return 0;
}
式中の4つの数値のうち、1つだけが実数、残り3つは整数となっている。 そして、最初の2式は正しい答が得られているが、最後の2式は間違っている。 この違いは、除算が実数で計算されているか整数で計算されているかの違いから来ている。
1つ目の式の 108.0/100 と2つ目の式の 108/100.0 は、どちらも実数の除算となるので、答は1.08 である。
他方、3つ目と4つ目の 108/100 は整数の除算であり、答は 1 になってしまう。
この違いが尾を引き、最終的な金額に反映されているのである。

練習問題

ロト6は、1〜43の数値の中から6つを選ぶくじである。 6つとも当たりの数値に一致すると一等となる。 43個の数値から6つ選ぶ選び方は何通りあるか計算し「選び方は○○通り」と表示するプログラムを作りなさい (つまり、436を計算しなさいということ)。