これまでのプログラムではデータとして主に整数だけを扱ってきました。 Java ではこれ以外に実数 (浮動小数点数) もデータとして扱うことができます。 計算機の世界では、整数と実数は本質的に違った種類のデータとして扱われます。 今回は、整数と実数の性質の違いとプログラムの書き方について説明します。
Java では整数型 int は 2 の補数表現による 32 ビットの整数です。 この表現では、最小 2-31 = -2147483648 ≒ -21億、 最大 231-1 = 2147483647 ≒ 21億の範囲を表すことができます。 計算機で扱っているデータは有限個のビットで表されているので、 扱える数の範囲に制限があり、 この範囲を越えるとまったくでたらめな結果になります。 これをオーバフロー (overflow) と呼びます。
オーバフローが起こってもそのことがすぐに分かるとは限りません。 一見すると正しそうだが実は間違っているということもあります。 型の表すことができる有効範囲に気を配っておく必要があります。
オーバフローした値は例えば次のようになります。
30億: -1294967296、
40億: -294967296、
50億: 705032704、
このように正負すら変わってしまうので、
オーバフローしたときの計算結果にはまったく意味がありません。
整数型には byte, short, int, long があります。 これらは表現できる値の範囲が異なるだけで、 整数としての性質は共通です。
浮動小数点型は小数 (小数点以下の数字がある数) や非常に大きい、あるいは 非常に小さい数を扱うことができます。 浮動小数点型を実数型と呼ぶ場合もあります。 以下では、浮動小数点数と実数を同じ意味で用います。
計算機の世界では、整数と実数はビット表現の方法が異なります。 例えば、整数の 3 と実数の 3.0 はまったく違ったビット表現になっています。 このため、データが整数の場合と実数の場合では、 計算機内部での計算方法が異なるのです。
浮動小数点型には他に float があります。 性質は double とほぼ同じであるため、 この授業では取り上げないことにします。
浮動小数点型の変数の宣言には double というキーワードを用います。 int による整数型変数の宣言と同じ形式です。
double a; double b = 3.0;
また、プログラムの中に実数を直接書きたい場合は次のように書きます。
1.0 3.1415926553589793 6.02e23 1e-14
小数点が書かれていれば、実数の値を意味します。 また、 6.02e23 は 6.02×1023 の意味です。 e の後に 10 のべきを書きます。 ここでも同様に「e」があれば、実数の意味になります。 1e-14 は 1.0×10-14を表します。
四則演算 (+, -, *, /) や比較 (<, <=, >, >=) の書き方は整数と共通です。 余りを求める % 演算子も使うことができます。
数値を表示する際、桁数、基数といった 書式 (フォーマット) を指定して画面表示するために System.out.printfを使います。 詳しくは、教科書 pp.353-360を参照してください。 System.out.printf は、次のように使います。
System.out.printf([書式指定文字列], [表示したい式1], [表示したい式2], ...);
例えば、int型の変数の値を表示するには次のように書きます。
System.out.printf("kの値は %d です%n", k); System.out.printf("kの値は %d 、jの値は %d です%n", k, j);
書式指定文字列の「%d」の部分がカンマで区切った後ろの式の値に順に 置換されて表示されます。 %d は、10進数で表示することを表し、%nは改行することを表しています。
代表的な書式の例をいくつか示します。
System.out.printf("%d", k);
System.out.printf("%10d", k);
System.out.printf("%010d", k);
System.out.printf("%x", k);
System.out.printf("%f", x);
System.out.printf("%.4f", x);
System.out.printf("%10.4f", x);
int k = 7; double x = 2.5; double y; y = k * x; (yの値は 17.5)
例えば、k が整数型の変数、x が浮動小数点型の変数であり、 k * x のような計算が行われた場合、 最初に k が実数に変換され、 x との掛け算が行われます。 このような場合の掛け算は実数での掛け算になります。
double x; int k = 1; x = k;
x = k; のように、 整数型変数を浮動小数点型変数に代入することができます。 この場合、自動的に k が実数に変換され、結果が x に代入されます。
double x = 1.2; int k; k = x; // ←小数点以下の数が不用意に失われるためコンパイルエラーとなる
一方、k = x; のように、 浮動小数点型変数を整数型変数に代入したい場合、 あらかじめ型の変換を明示的に行っておく必要があります。 また、このような場合、小数以下の値が切り捨てられたり、 値の精度が失われる恐れがあることに注意する必要があります。
このような場合、変換したい値の前に (int) のように型を表すキーワードをカッコで囲み、 何型に変換するかを書いておきます。 例えば、 x を k に代入したい場合、次のように書きます。
double x = 1.2; int k; k = (int)x; // ←明示的にxの型を変換すること (double→int) を指示 ...kの値は1となる...
(int) のように型の変換を行うための演算子をキャスト演算子と呼びます。
他の型についても同様です。 精度の高い型に精度の低い型の値を代入する場合は自動的に変換が行われますが、 逆の代入を行うにはキャスト演算子を用い変換をする必要があります。
ここで、注意してほしいことがあります。 整数同士の計算の結果は常に整数になるということです。
例えば k=7, j=3 でどちらも整数の場合、浮動小数点型変数 x に対して、
int k = 7; int j = 3; double x; x = k / j;
という計算を行う場合を考えてみましょう。 浮動小数点数で計算した7÷3の答え2.333333… を x に代入したいのであれば、このプログラムは間違いです。
このプログラムでは、 まず、 k / j の計算は整数で行われ整数 2 が求まります。 k と j が整数の場合、結果は小数点以下が切り捨てられ整数となるためです。 次に、整数 2 を実数に変換した値 2.0 が x に代入されてしまいます。
k / j の計算を浮動小数点数で行う方法の一つは、 型変換を行うキャスト演算子を使う計算です。 キャスト演算子を使い、あらかじめ j を浮動小数点数に変換してから計算を行います。
int k = 7; int j = 3; double x; x = k / (double)j;
もう一つの方法は、別の double 型変数 y を用い、
int k = 7; int j = 3; double x; double y; y = j; x = k / y;
のようにすれば良いのです。x に入る値は 2.3333333… になります。
浮動小数点型では値の範囲より、値の精度に限りがあることの方が重要です。 整数型の値はオーバフローさえしなければ正確なのですが、 実数では、例えば double 型の場合 1.8×10308 まで表現できるとはいっても、 その範囲の値すべてを正確に表現できるわけではありません。
浮動小数点型の値は、ある一定の桁数までしか正確ではありません。 これは物理や化学などで使われる有効数字の考え方と同じです。 例えば、観測結果が 3 桁しか信頼できない場合 1.23×1023 のような表記法で書きます。これは値の正しさが 3 桁までしか信用できないという意味です。 この例では有効数字が 3 桁であると言います。
計算機内部の実数の表現法はこれと同様の方式です。 1.23 のような数を仮数、1023 のような数を指数と呼び、 値を仮数と指数に分けて表現しています。 浮動小数点型では、仮数で表現できる値までの精度しか持たないことになります。
Java では double 型の有効数字は 2 進で 53 桁、10 進でおよそ 16 桁です。 有効数字 16 桁で表現できる数だけを扱うのが浮動小数点型です。
このため、 常に値自身に約 1/1016 の割合の誤差を含んでいることに注意が必要です。 この誤差は計算方法によってはさらに大きくなる場合があります。 0.123456789-0.123456788の結果0.000000001の有効数字は1桁しかありません。 これに似た計算をしているとほとんど信頼できる数字が無くなり、 誤差しか残らないということもあり得ます。
また、計算機の内部表現は 2 進法なので、 例えば、 0.01 は正確に表現することはできずに近似値に置き換えられます。 したがって、次のような for 文を書くと、 思ったような結果にならないことがあります。
for (x = 0.0; x != 1.0; x = x + 0.1) ....
さらに、計算のやり方によってはわずかに違う誤差が生じることがあります。 例えば、 a + b に c を加えた結果と b + c に a を加えた結果が一致するとは限りません。 つまり a + b + c ≠ a + (b + c) であり、普通の数学の公理が成立しないのです。