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

講義資料

- 配列を用いた大量データの扱い

今まで学んだ知識では、 変数を宣言したぶんのデータの個数しか扱うことができませんでした。 例えば変数を1000個宣言し使うのは非現実的です。 Java で大量のデータを扱うための基本的な機能が「配列 (Array) 」です。 配列は同じ種類のデータの集まりに一つの名前をつけ、 ひとまとめにして扱うものです。 そして、一つ一つのデータは番号で指定します。

配列についての文法的な説明は 「エッセンシャルJava」の pp.50〜65 を参照してください。

- 例題: 与えられた数の合計を求める

プログラムの先頭で int 型の配列を用意し、 複数の整数を代入しておきます。 その合計を求めるプログラムを考えてみます。 さらにこのプログラムでは、合計を表示した後、 元の数を改めて表示することにします。

ファイル名は SumAndList.java とします。

2 つの for 文があります。 1 つめの for 文で a の各要素の合計を求め、 2 つめの for 文で各要素の値を表示しています。 for 文の 2 番目の式の a.length は 配列 a の要素数を求める書き方です。

いずれの for 文も、a[0], a[1], a[2], ..., a[4] の順に配列 a の要素を処理しています。 この for 文の中身は i を 0 から 1 ずつ増やしていき、 i が 4 になるまで合計 5 回繰り返されます。

for 文の形は 2 つとも同じで、次のようになっています。

for (i = 0; i < a.length; i++)
    処理

この形が配列の要素を 0 から順に処理する際の標準的な書き方なので、 身につけておくべきです。

ところで、配列の宣言、初期化の書き方には何種類かあります。 このプログラムの 5,6 行目を 1 つにまとめて、

int[] a = new int[5];

と書くこともできます。 また、宣言と初期化を同時に行い、 5 〜 11 行目をまとめて、

int[] a = new int[]{3, 5, 6, 1, 9};

と書くこともできます。

int[] a;int a[]; は同じ意味です。 前者の書き方がお勧めです。

配列は、宣言された要素の数を越えて使うことはできません。 また、要素の番号に負の数を使うことはできません。 a[i]i のように、 配列要素の番号を示す整数を「添字 (そえじ) 」と呼びます。 添字は 0 以上、要素数未満である必要があります。

範囲外の配列の要素にアクセスしようとすると、 Java では例外 (Exception) と呼ばれる一種のエラーが発生し、 プログラムが強制的に終了してしまいます。 このようなことを防ぐために、 配列にアクセスする際には、 添字がその配列の範囲内にあるかどうかについて常に気を配る必要があります。

- 例題: 最小の要素を探す

第 2 回目 (条件判定) の例題で、 3 つの値の最大値を求めるプログラム を取り上げました。 この例題の 3 番目のプログラムの考え方をもとに、 任意の大きさの配列から最小の値を探し出すプログラムを考えてみましょう。

ファイル名は SearchMinimum.java とします。

for 文で、要素を一つ一つ比較し、 変数 minValue に、 それまでに見つかった最小の値を入れておくようにします。 また、最小値の入っている要素の番号を minIndex に入れておきます。 全ての要素を検査し終わり、for 文を抜けると、 変数 minValue に、最小の値が入っていることになります。

- コマンドライン引数

コマンドライン引数 (ひきすう) (Command Line Argument) とは、 Java のプログラムを実行を開始するときに、 指定した文字列をプログラムに渡すことができるものです。 これにより、実行時にプログラムの動きを変えたり、 値をプログラムに渡すことができます。

例えば、プログラムが次のコマンドで起動されたとします。

java AClass filename condition

このコマンドには通常の Java プログラムの実行コマンドに加えて、 filename と condition の 2 つの引数があります。 UNIX のコマンドは一般にスペースで引数を区切る約束になっています。

Java のプログラムの始めには public static void main(String[] args) という記述でしたね。ここに

String[] args 

という配列の宣言のような記述があることに気がついたでしょう。 コマンドライン引数は配列 args の要素として取り出すことができます。

args の要素は文字列です。 この例では、args[0] には "filename" が、 args[1] には "condition" が入ります。 このように「n 番目の引数」は args[n] として プログラム内で取り出すことができます。

- 例題: コマンドライン引数から与えられた数の合計を求める

今回の 1 つめの例題 「引数から与えられた数の合計を求める」 を改造して、コマンドラインから入力された数の合計を求めるプログラムを作成します。

ここで、コマンドライン引数の配列 args に注目すると、 この配列は String すなわち文字列であることが分かります。 Java では変数には型があり、 整数や文字列などの型で決められたデータしか扱うことはできない、 ということを思い出してください。 文字列は、そのままでは数値として扱うことやその逆はできないのです。

例えば、

java AClass 10

のように実行し、引数に「10」と入力した場合、 args[0] は文字列の "10" となります。 文字列の "10" は、 '1' と '0' の文字が並んだデータであり、 同じ「10」 でも、文字列の "10" と数値の 10 は計算機の中での扱いは別物なのです。 これを数値としての 10 に変換する必要があります。

数字の並んだ文字列を解釈し int 型整数に変換するには、 Integer.parseInt という機能を用います。

例えば、 文字列 args[0] を、 int 型の整数に変換し変数 a に代入するには次のように書きます。

a = Integer.parseInt(args[0]);

以上のことを踏まえ、コマンドライン引数の文字列を一つ一つ取り出し、 Integer.parseInt で数値に変換しながら合計を求めるプログラムは 次のようになります。 ファイル名は SumAndListFromArgs.java としています。

class SumAndListFromArgs {
    public static void main (String[] args) {
	int i;
        int sum = 0;
        for (i = 0; i < args.length; i++) 
            sum = sum + Integer.parseInt(args[i]);

        System.out.println("The sum of following values is " + sum);

        for (i = 0; i < args.length; i++) 
            System.out.print(Integer.parseInt(args[i]) + " ");
        System.out.println();
    }
}

以下の例のように、引数に任意の個数の整数を入力し実行すると、 合計値が表示されます。

java SumAndListFromArgs 2 4 6 8 10
The sum of following values is 30
2 4 6 8 10 

- 整数型 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を参照してください。

k が整数型の変数、x が浮動小数点型の変数だとします。 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) であり、普通の数学の公理が成立しないのです。