| + | 加算 |
| - | 減算、符号反転 |
| * | 乗算 |
| / | 除算 |
| % | 剰余 |
と、省略形としての +=、-=、*=、/=、%=という 演算子があると既に述べた。
int a = 10;
int b = 4;
double c = 10;
double d = 4;
printf("%d\n", a / b);
printf("%g\n", c / d);
の結果は、
2 2.5となる。
「%」演算子は、割ったときの余りを返す。すなわち、
int a = 10;
int b = 4;
printf("%d\n", a % b);
の結果は、
2となる。「%」演算子は浮動小数点数には使えない。
i = i + 1;
の省略形として
i += 1;
と書けると既に述べた。
このような「+1」または「-1」の場合は更に省略でき、
i += 1;
i -= 1;
は、
i++;
i--;
または
++i;
--i;
と省略できる。
注意
なお、この書き方は次のようにそれ自身が値を持ち式の中で 使うことが出来るが、その値は前置記法と後置記法で異なる。a = 1; b = 1; c = a++; d = ++b; printf("%d\n", a); printf("%d\n", b); printf("%d\n", c); printf("%d\n", d);結果は、2 2 1 2となる。前置記法は「変更後の値」、後置記法は「変更前の値」をとる。 どちらでも演算対象の変数(a、b)が+1されることに 変りは無い。すなわち、 「 c = a++; 」は、c = a; a = a + 1;と、「d = ++b;」は、b = b + 1; d = b;と同じ動作をする。
| char | 8bit整数(文字型) |
| short int | 16bit整数 |
| long int | 32bit整数 |
| int | 32bit整数 |
| float | 32bit浮動小数点数 |
| double | 64bit浮動小数点数 |
があると既に述べた。これは現在最も普及しているシステムでの 数値であり、C言語の規定では各数値のbit数には以下の 規定があるのみである。
unsigned int a; signed char b;のようにunsignedまたはsignedを 付加でき、unsignedならば0または正の数を、 signedは正負の数を表現できる。 何も付けない場合は、short int、 long ing、intの場合はsigned扱いに なる。charがどちらになるかは処理系依存である。
まず、unsigned int型は計算機内でどう表現されているか説明する。 int型は一般に 32bit = 4byteの記憶領域を使って記憶される。 以下に、bit patternと数値との対応を示す。
| bit pattern | 数値 |
|---|---|
| 11111111111111111111111111111111 | 4294967295 |
| 00000000000000000000000000000010 | 2 |
| 00000000000000000000000000000001 | 1 |
| 00000000000000000000000000000000 | 0 |
普通の32桁の2進数である。 32個の各bitが次のような重みを持っていると思えばよい。
| 231 | 230 | 229 | ..... | 21 | 20 |
上記のように、nビットで 0~2n-1の範囲の数を表現できる。 すなわち、Cのunsignedな整数型は、
| char | 0~255 |
| short int | 0~65535 |
| long int | 0~4294967295 |
の範囲の数が表せることになる。
次に、signed int型は計算機内でどう表現されているか説明する。 1bitを削ってそこを符号bitにし、残りの31bitに絶対値を入れればいいような 気もするが、それとは異なるフォーマットになっている。 以下に、bit patternと数値との 対応を示す。
| bit pattern | 数値 |
|---|---|
| 01111111111111111111111111111111 | 2147483647 |
| 00000000000000000000000000000010 | 2 |
| 00000000000000000000000000000001 | 1 |
| 00000000000000000000000000000000 | 0 |
| 11111111111111111111111111111111 | -1 |
| 11111111111111111111111111111110 | -2 |
| 10000000000000000000000000000000 | -2147483648 |
このような負の数の表現形式を「2の補数形式」と呼ぶ。 32個の各bitが次のような重みを持っていると思えばよい。
| -231 | 230 | 229 | ..... | 21 | 20 |
上記のように、2の補数形式の場合、nビットで -2n-1~2n-1-1の範囲の数を表現できる。 すなわち、Cのsignedな整数型は、
| char | -128~127 |
| short int | -32768~32767 |
| long int | -2147483648~2147483647 |
の範囲の数が表せることになる。
C言語では浮動小数点数の格納形式に明確な規定は無いが、 最近のCPUのほとんどはIEEE Std. 754で定められた形式で格納されている。 以下、それについて説明する。
doubleの一般的な格納形式は、符号(±)に1bit、指数部に11bit、仮数部に 52bit使って表す。全部で64bit=8byteである。
| 1(符号) | 11(指数部) | 52(仮数部) |
であるから、計算機内では、0.00011001100110011001100.... = 1.1001100110011001100....× 2-4
| 0 | 1111111011 | 1001100110011001100110011001100110011001100110011001 |
のように格納されている。指数部の「1111111011」は、「-4+1023=1019」 を2進数にしたものである。
有効数字は、2進数で53桁である。10進数に直すと、
log10(253) ≒ 15.95より約16桁であると考えられる。
ついでに、floatは、全部で32bit=4byteで、
| 1(符号) | 8(指数部) | 23(仮数部) |
のようになっている。 有効数字は、2進数で24桁、10進数に直すと、
log10(224) ≒ 7.22より約7桁。
例えば、代入の場合、
double d;
int a;
a = 2;
d = 3.14;
d = a;
a = d;
のように、異なった型の変数を代入すると、
適当に変換されてから(自動型変換という)代入される。この場合、
double d;
int a;
a = 2;
d = 3.14;
a = (int)d;
d = (double)a;
と書くことも出来る。異なった型同士の代入の場合は、思わぬ自動型変換
によるバグを防ぐためにも、キャストを用いて書いた方が良い。次に、2項演算子の両辺に異なった型が用いられた場合を考える。
double d;
int a;
a = 2;
d = 3.14;
printf((double)a * d);
printf(a * (int)d);
printf(a * d);
この実行結果は、
6.28 6 6.28となる。一般に異なった型同士の演算を行った場合は、このようにより 表せる数が多い方の型(double)に他方(int)が自動的に 変換されてから計算される。
a = 2;
a = a + 0.3;
の「2」や「0.3」ようなプログラム中の定数は、「小数点が
含まれていなければint型、含まれていればdouble型」と
解釈される。異なる型どうしの代入、演算については、
前節のように普通は自動的に変換されるので問題は起きないが、
ときとして予想外な問題を引き起こす。
例えば、数値を0~1.0まで0.1刻みで変化させてそのsinを表示しようとして、
int i;
for (i=0; i<=10; i++) {
printf("%g\n", sin(i / 10));
}
と書くと、「i」も「10」もint型なので、
整数型の割算(小数点以下切捨て)が
行われ、期待通りの結果にならない。
「i / 10」が「i / 10.」と小数点付きで書かれていれば、
この問題は起こらない。
結論として、プログラム中に実数の定数を書くときは、必ず小数点を付ける 習慣を付けると良い。
なお、「10-15」や、「6.02×1023」などの 非常に大きい(小さい数)をプログラム中に書くときは、 「1e-15」や、「6.02e23」 などの「e表記」が使える
現在の計算機のほとんどは、既にfloatとdoubleの 計算速度は違いが無くなっているので、計算速度を稼ぐ目的で floatを使用しても意味は無い。 ただ、記憶に使う領域の大きさは違うので、メモリを大量に用いる 計算でfloatを用いる意味はある。
また、実数は2進数で格納されるので、10進数の世界に 慣れている我々にとっては思わぬ現象が起きることがある。 上の「(1./3.)*3.」が1にならないのは普通の電卓なんかでも 観察できるのでそれほど違和感を持たないと思う。 ところが、例えば「1./10.」は、
double x;
x = 0.;
while (1) {
printf("%g\n", x);
if (x == 1.) break;
x = x + 0.1;
}