数値と算術演算子


前の 簡単なプログラム で数値と算術演算子については簡単に触れたが、ここでより 詳しく説明しておく。

算術演算子

算術演算子は、

+加算
-減算、符号反転
*乗算
/除算
%剰余

と、省略形としての +=-=*=/=%=という 演算子があると既に述べた。

除算と剰余

ここで注意すべきは、「/」演算子である。整数型同士の除算は、 結果も整数型となり、小数点以下は切り捨てられてしまう。 浮動小数点型なら普通に計算される。
    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
となる。前置記法は「変更後の値」、後置記法は「変更前の値」をとる。 どちらでも演算対象の変数(ab)が+1されることに 変りは無い。すなわち、 「 c = a++; 」は、
    c = a;
    a = a + 1;
と、「d = ++b;」は、
    b = b + 1;
    d = b;
と同じ動作をする。

整数型と浮動小数点型

主な変数の型の種類は、

char8bit整数(文字型)
short int16bit整数
long int32bit整数
int32bit整数
float32bit浮動小数点数
double64bit浮動小数点数

があると既に述べた。これは現在最も普及しているシステムでの 数値であり、C言語の規定では各数値のbit数には以下の 規定があるのみである。

特に、intに注目。int = short intで良いし、 過去にそういうシステムも存在した(例えばMS-DOS)。 intは、一般的にそのCPUで最も自然な大きさに選ばれる。

整数型

Cの整数型には、
unsigned int a;
signed char b;
のようにunsignedまたはsignedを 付加でき、unsignedならば0または正の数を、 signedは正負の数を表現できる。 何も付けない場合は、short intlong ingintの場合はsigned扱いに なる。charがどちらになるかは処理系依存である。 まず、signed int型は計算機内でどう表現されているか説明する。 int型は一般に 32bit = 4byteの記憶領域を使って記憶される。以下に、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

の範囲の数が表せることになる。

次に、unsigned int型は計算機内でどう表現されているか説明する。 以下に、bit patternと数値との対応を示す。

bit pattern 数値
11111111111111111111111111111111 4294967295
00000000000000000000000000000010 2
00000000000000000000000000000001 1
00000000000000000000000000000000 0

32個の各bitが次のような重みを持っていると思えばよい。

231 230 229 ..... 21 20

上記のように、nビットで 0〜2n-1の範囲の数を表現できる。 すなわち、Cのunsignedな整数型は、

char0〜255
short int0〜65535
long int0〜4294967295

の範囲の数が表せることになる。

浮動小数点型

次に、floatdoubleについて説明する。 これらは、基本的に「浮動小数点形式」と呼ばれる形式で格納されている。 例えば、「1234.5」に対する「1.2345×103」のように、小数点の位置を一番 左の数値の右側に移動し、それに指数を掛けた形式である。 この「1.2345」の部分を「仮数部」といい、「103」の部分を「指数部」と いう。ここで10進数で書かれているのは説明のためであり、 実際には「1.01010011×23」のように2進数で表現されている。

C言語では浮動小数点数の格納形式に明確な規定は無いが、 最近のCPUのほとんどはIEEE Std. 754で定められた形式で格納されている。 以下、それについて説明する。

doubleの一般的な格納形式は、符号(±)に1bit、指数部に11bit、仮数部に 52bit使って表す。全部で64bit=8byteである。

1(符号) 11(指数部) 52(仮数部)

例えば、0.1は2進数で書くと
  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桁。

型変換

上記のようにCには様々な数値型があるが、今までそれらを混在して 扱った場合について述べてこなかった。

例えば、代入の場合、

    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.02e-23」 などの「e表記」が使える

浮動小数点型の取り扱いに関する注意

floatdouble

昔は、float(慣用的に単精度と呼ばれる)と double(慣用的に倍精度と呼ばれる)では計算速度がかなり 違い、計算速度を稼ぐために普段はfloatを用い、 特別に精度が欲しいときだけdoubleを使うことが多かった。

現在の計算機のほとんどは、既にfloatdoubleの 計算速度は違いが無くなっているので、計算速度を稼ぐ目的で floatを使用しても意味は無い。 ただ、記憶に使う領域の大きさは違うので、メモリを大量に用いる 計算でfloatを用いる意味はある。

計算精度について

実数計算は有限精度で行われる、つまりあらゆる計算には誤差が入る、 という点には、常に注意する必要がある。 例えば、「1./3.」は無限小数になるのでどこかで打ち切る必要があり、 よって、「(1./3.)*3.」は一般に1にならない。

また、実数は2進数で格納されるので、10進数の世界に 慣れている我々にとっては思わぬ現象が起きることがある。 上の「(1./3.)*3.」が1にならないのは普通の電卓なんかでも 観察できるのでそれほど違和感を持たないと思う。 ところが、例えば「1./10.」は、

となって、無限小数になる。つまり、「0.1」は double型やfloat型では正確に表現できない。これによって、 例えば次のプログラムは正常に動かず無限ループに陥ることになる。
    double x;

    x = 0.;
    while (1) {
        printf("%g\n", x);
        if (x == 1.) break;
        x = x + 0.1;
    }

数値と算術演算子 / kashi@mn.waseda.ac.jp