先日の講義でプログラムと型の話がありました。
一部の方には、実際に何が問題になり得るかを口頭で説明しましたが、念の為実際に問題になる実例を示します。 なお、本文の内容は授業上必須ではないはずですので、お暇な方だけ、どうぞ。
ここでは来週説明する予定であるifとforが出ていますので、今は雰囲気だけ感じてください。
#forについてはp121、ifについてはp128に、それぞれ説明があります。
一時期話題になった、FizzBuzzというプログラムを例に挙げてみます。
プログラムが出力する文字列のルールは以下の通り
- 1から順に、整数(自然数)を出力すること
- ただし、その整数が3で割り切れる場合には、数字の代わりに「Fizz」と出力すること
- 5で割り切れる場合は、Buzzと出力すること
- 3でも5でも割り切れる場合はFizzBuzzと出力すること
これだけです。
1〜20に対してFizzBuzzを解くJavaのコード例は以下の通り。
class Hello {
public static void main(String[] args) {
for (short i = 1; i < 20; i++){
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.printf("%d\n", i);
}
}
}
}
Console を見ると以下のように出力され、プログラムは終了します
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
forループで
for (short i = 1; i < 20; i++){
と指定しているので、1から20「未満」のFizzBuzzの結果が出ます。 とりあえずルールどおりの結果である点を確認してください。(i <= 20 なら、20の結果も出ます)
さて、今度は1〜40000で結果を見てみたいと思ったとしましょう。 上のプログラムの for の部分を変更し、「i < 40000」として、実行してみます。
class Hello {
public static void main(String[] args) {
for (short i = 1; i < 40000; i++){ // <----- ここの数字を変えただけですよう
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.printf("%d\n", i);
}
}
}
}
このプログラムは「終了しなくなります」。
変な値が登場するというレベルではありません。 画面下で永久に文字列が表示され続けてしまいます。
「どちらか」をやれば、プログラムが停止します
- Console というEclipse下方の窓っぽいのの右側にある赤い□をクリックする
- もし上のボタンがわからない場合は、Eclipse自体を一旦終了する
くやしーので、出力の一部を切り取ってみます
Buzz
-18844
Fizz
-18842
-18841
FizzBuzz
-18839
-18838
Fizz
-18836
Buzz
Fizz
-18833
-18832
Fizz
Buzz
-18829
Fizz
-18827
-18826
何かマイナスの数字が……ええと、おかしいなぁ
for (short i = 1; i < 40000; i++){
1〜40000までって書いてあるんだけどなぁ。
shortで表現できる整数の最大値を教科書で見てみましょう。 (p112)
-32768〜32767ってありませんかね。
class Hello {
public static void main(String[] args) {
for (short i = 32760; i < 32767; i++){ // <----- ここの数字をまた変えただけですよう
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.printf("%d\n", i);
}
}
}
}
これは、止まります。
class Hello {
public static void main(String[] args) {
for (short i = 32760; i < 32768; i++){ // <----- ここの数字をまた変えただけですよう
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.printf("%d\n", i);
}
}
}
}
これは、止まりません。
次のプログラムは、この問題についてのヒントを与えてくれます。
class Hello {
public static void main(String[] args) {
short i = 32767;
System.out.println(i);
i++;
System.out.println(i);
}
}
結果は以下の通り。
32767
-32768
二行目のSystem.out.println(i) の中身が「負の値」になっている点は注目に値します。
forの説明がまだなのでアレですが、何が起きたかを理解してみます。
class Hello {
public static void main(String[] args) {
for (short i = 32760; i < 32768; i++){ // <----- ここの数字をまた変えただけですよう
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.printf("%d\n", i);
}
}
}
}
数字にだけ着目するべく、以下のようなプログラムに変えてみます。
class Hello {
public static void main(String[] args) {
for (short i = 32760; i < 32768; i++){ // この行が大事。数字が、大事
System.out.println(i);
}
}
}
shortが対応できる値は 32767 までです。
しかし、iがその最大値である 32767 であるとき、 i < 32768 は boolean の false を返します。 (p112の表の一番下にしれっと書いてある「真偽値」です。)
プログラムは、i++してからもう一度forの中身を実行します。
ところで、short iの中身が 32767 だったときのインクリメントの結果ってなんでしたっけ。 もう一度ここまでのプログラムを眺めてみてください。
実は-32768なんですよね。そのプログラムをもう一度そのまま再掲します
class Hello {
public static void main(String[] args) {
short i = 32767;
System.out.println(i); // 32767 (`・ω・´)
i++; // あっ (๑´╹‸╹`๑)
System.out.println(i); // -32768 (´・ω・`)
}
}
というわけで、ここで悲劇が始まります。iは-32768になり、for文は引き続きこの処理を続けます。 やがてこの変数 i は、再び 32767 に到達し、 i < 32768 のチェックを受けます。
ああ、終わらないなこれ。
#このように、forのような「ループ」命令が永久に終わらない状態を良く「無限ループ」と言います。
今回のケースでは、単にintを使えば大丈夫です。
class Hello {
public static void main(String[] args) {
for (int i = 1; i < 40000; i++){ // <----- intを使ってますよう
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.printf("%d\n", i);
}
}
}
}
intでなぜ大丈夫なんでしょうか。
再び教科書p112を見ると、intが表現できる整数の範囲が書かれています。 少なくとも40000は軽く表現できるので、さきほどのような問題が発生しません。
変数が保持できる値には限界があります。 そして、変数を使って計算している際、計算結果が想定しないほど大きくなったりして変数が正しく値を保存できなくなることがあります。
この現象を、業界ではよく「オーバーフロー」(「算術オーバーフロー」と呼びます。 変数が対応できる範囲から数字が「あふれちゃった」のでした(overflow = 溢れる、洪水)
オーバーフローに関する難しい説明はWikipediaをどうぞ http://ja.wikipedia.org/wiki/%E7%AE%97%E8%A1%93%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC
もしまだ分からなければ来週、講師や補助講師に聞いてみてください。