コンピュータ基礎および演習II

例題

これまでのプログラムではデータとして整数と文字、 文字列だけを扱ってきました。 Java ではこれ以外に実数 (浮動小数点数) もデータとして扱うことができます。 計算機の世界では、整数と実数は本質的に違った種類のデータです。 今回は、整数と実数の性質の違いとプログラムの書き方について説明します。

- 整数型 int

Java では整数型 int は 2 の補数表現による 32 ビットの整数です。 この表現では、最小 2-31 = -2147483648 ≒ -21億、 最大 231-1 = 2147483647 ≒ 21億の範囲を表すことができます。

このように、計算機で扱っているデータは有限個のビットで表されているので、 扱える数の範囲に制限があり、 この範囲を越えるとまったくでたらめな結果になります。 これをオーバフローと呼びます。

オーバフローが起こってもそのことがすぐに分かるとは限りません。 一見すると正しそうだが実は間違っているということもあります。 型の表すことができる有効範囲に気を配っておく必要があります。

オーバフローした値は例えば次のようになります。
30億: -1294967296、
40億: -294967296、
50億: 705032704、
このように正負すら変わってしまうので、 オーバフローしたときの計算結果にはまったく意味がありません。

整数型には byte, short, int, long があります。 これらは表現できる値の範囲が異なるだけで、 整数としての性質は共通です。

- 浮動小数点型 double

浮動小数点型は小数 (小数点以下の数字がある数) や非常に大きい、あるいは 非常に小さい数を扱うことができます。 浮動小数点型を実数型と呼ぶ場合もあります。 以下では、浮動小数点数と実数を同じ意味で用います。

計算機の世界では、整数と実数はビット表現の方法が異なります。 例えば、整数の 3 と実数の 3.0 はまったく違ったビット表現になっています。 このため、データが整数の場合と実数の場合では、 計算機内部での計算方法が異なるのです。

浮動小数点型には他に float がありますが、 この授業ではとりあげないことにします。

浮動小数点型 double を使ったプログラムの書き方

浮動小数点型の変数の宣言には double というキーワードを用います。 int による整数型変数の宣言と同じ形式です。 これについては、「エッセンシャルJava」pp.36〜37 を参照してください。

プログラムの中に実数を直接書きたい場合は次のように書きます。

1.0
3.1415926553589793
6.02e23
1e-14

小数点があれば、実数型の値を意味します。 6.02e23 は 6.02×1023 の意味です。 e の後に 10 のべきを書きます。 「e」があれば、実数の意味になります。 1e-14 は 1.0×10-14を表します。

四則演算や比較の書き方は整数型と共通です。 余りを求める % 演算子も使うことができます。

- 整数と実数の混合計算

四則演算における型の変換

「エッセンシャルJava」 p.79を参照してください。

例えば、k が整数型の変数、x が浮動小数点型の変数だとして、 k * x のような計算が行われた場合、 最初に k が実数に変換され、 x との掛け算が行われます。 このような場合の掛け算は実数での掛け算になります。

代入における型の変換

「エッセンシャルJava」 p.83を参照してください。

x = k; のように、 整数型変数を実数型変数に代入することができます。 この場合、自動的に k が実数に変換され、結果が x に代入されます。

一方、 k = x; のように、 実数型変数を整数型変数に代入したい場合、 あらかじめ型の変換を行っておく必要があります。 また、このような場合、小数以下の値が切り捨てられたり、 値の精度が失われる恐れがあることに注意する必要があります。

変換したい値の前に (int) のように型を表すキーワードをカッコで囲み、 何型に変換するかを書いておきます。 例えば、 xk に代入したい場合、次のように書きます。

k = (int)x;

(int) のように型の変換を行うための演算子をキャスト演算子と呼びます。

他の型についても同様です。 精度の高い型に精度の低い型の値を代入する場合は自動的に変換が行われますが、 逆の代入を行うにはキャスト演算子を用い変換をする必要があります。

代入と演算を行う際の型の変換

ここで注意してほしいことは、 整数と実数の混合計算における変換は 2 つの値についての演算を対象に行われるということです。

たとえば k=7, j=3 でどちらも整数の場合、浮動小数点型変数 x に対して、

x = k / j;

という計算を行う場合、まず k /j の計算は整数で行われ整数 2 を得ます。 kj が整数の場合、結果は整数近似の値になってしまいます。 次に、整数 2 を実数に変換した値 2.0 が x に代入されます。 もし、 2.333333… を x に代入したいのであれば、これは間違いとなります。

このような場合は k / j の計算を実数で行う必要があります。 その方法の一つは、型変換を行うキャスト演算子を使う方法です。 キャスト演算子を使い、あらかじめ j を実数に変換してから計算を行います。

x = k / (double)j;

もう一つの方法は、別の double 型変数 y を用い、

y = j;
x = k / y;

のようにすれば良いのです。x に入る値は 2.3333333… になります。

- 浮動小数点型の精度

浮動小数点型では値の範囲より、値の精度に限りがあることの方が重要です。 整数型の値はオーバフローさえしなければ正確なのですが、 実数では、例えば double 型の場合 1.8×10308 まで表現できるとはいっても、 その範囲の値すべてを正確に表現できるわけではありません。

浮動小数点型の値は、ある一定の桁数までしか正確ではありません。 これは物理や化学などで使われる有効数字の考え方と同じです。 例えば、観測結果が 3 桁しか信頼できない場合 0.123×1023 のような表記法で書きます。これは値の正しさが 3 桁までしか信用できないという意味です。 この例では有効数字が 3 桁であると言います。

計算機内部の実数の表現法はこれと同様の方式です。 0.123 のような数を仮数、1023 のような数を指数と呼び、 値を仮数と指数に分けて表現しています。 浮動小数点型では、仮数で表現できる値までの精度しか持たないことになります。

Java では double 型の有効数字は 2 進で 53 桁、10 進でおよそ 16 桁です。 有効数字 16 桁で表現できる数だけを扱うのが浮動小数点型なのです。

このため、 常に値自身に約 1/1016 の割合の誤差を含んでいることに注意が必要です。 この誤差は計算方法によってはさらに大きくなる場合があります。 0.12345-0.12344の結果0.00001の有効数字は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) であり、普通の数学の公理が成立しないのです。

- 変数宣言

これまで示したプログラムでは、冒頭で変数宣言を行っていました。 実は、 Java ではプログラムの任意の位置で変数宣言を行うことができます。 例えば、次のような for 文を書くこともできます。

for (int i = 0; i < 10; i++)
    ....

- 文のまとめ

これまでにさまざな種類の文が登場しました。 ここで文のまとめをします。 また、文に関する注意もいくつか説明します。

文の名前 構文
空文 ;
式文 式 ;
複合文(ブロック)   { 文 文 文 ... 文 }
if 文(1) if ( 制御式 ) 文
if 文(2) if ( 制御式 ) 文 else 文
while 文 while ( 制御式 ) 文
for 文 for ( 式1 ; 式2 ; 式3 ) 文
break 文 break;

空文と式文をのぞいた文は、このまでに説明済みです。

式文は、式のみで構成されている文のことです。 式の直後にセミコロン (;) をつければ、文とすることができます。 代入も Java では式の一種として扱われることは以前に説明しました。 式文は、 if 文や while 文など特別なキーワードで始まる文以外の ほとんどすべての文が該当します。

セミコロン 1 つだけのものを空文と呼びます。 空文も立派な文の一種です。 空文を実行しても何も起こりません。 ここで空文を紹介する理由は、これを活用してほしいという意味ではなく、 うっかり空文を入れて間違ったプログラムになることを避けてほしいという意味です。 例えば、次のような for 文を書いたとします。

x = 0;
for (i = 1; i <= 10; i++);
    x = x + i;

一見すると x = x + i; を 10 回繰り返し、 1 から 10 の総和を求めるようですが、 実はそうではありません。 この for 文の中身は、 for の行の最後にあるセミコロン、つまり空文だけなのです。 x = x + iは for 文とは別の独立した文になるので、 i=11 の状態で 1 回だけ実行され x の値は 11 になります。

Java のようなプログラミング言語では、 セミコロン 1 つの有無でプログラムの意味が大きく変わってしまうことがあります。 細かい部分にも常に注意が必要なのです。

上の表の右側に「文」と書いてある個所がいくつもあります。 これらの個所には、どのような文を書いても構いません。 文法の面では文の組み合わせの仕方に特に制限はありません。 したがって、例えば、 for 文の中の文として、 if 文や別の for 文を書くことも自由なのです。for 文の中に if 文を書けば、繰り返しの各回で if 文を実行することになります。また、for 文の中に別の for 文を書けば 2 重の繰り返しになります。