練習問題3-xに対する補足

Javaに限らず、本格的なプログラム言語では、変数をはじめ、「データの型」ということを強く意識しないといけません。たとえば、「3÷2」は、当然1.5ですが、たとえば、

System.out.println(3/2);

とすると、1になります。こうした実世界との違いを必ず意識しないといけません。(ただし、Visual Basicのように、むしろ一般的な結果になるような動作をするプログラミング環境もあり、いずれにしても、それはそれでJavaとかとは違うという意識が必要になります。)

まず、なんで整数なんてあるのか、しかも、整数で4種類もあるのか(byte, short, int, long)ですが、1つには講義で説明があったように、プログラムでは整数で十分なことが多くあります。たとえば、数えるとか、回数にかかわることは整数で十分です。それに加えて、なるべくコンピュータ上での実行にかかる時間を短くしようという意図もあります。少数を含む計算より、整数の計算の方が早く済みます。また、同じ整数でも、一般にはバイト数が少ない型の方が速くなります。こうした効率を最大限に発揮させるためにも、整数型の利用は欠かせないのです。

ただ、そうした型がたくさんある場合、計算の上で、普通とは違う規則が適用されてしまいます。変数は型を宣言して定義するということと、キャストにより型を自由に変えることができるといのが基本ですが、それら以外の規則をまとめておきましょう。

数字を直接書くとどうなる?

これは決まっていることですが、整数を書くとint、小数点を含む数を記述するとdoubleになります。つまり、12というのはint型の数値で、12.2はdouble型です。また、12.0はdouble型になります。

数字を書いてそれを、shortやfloatなどにする方法もあります。たとえば、5.0fは、float型になりますが、数値のあとにある「f」が一種のキャストのようになります。ただ、キャストを使うほうが、より明示的であるので、そちがらお勧めです。

計算結果はどうなる

実は、正しくはきちんと定義されているのですが、まずは同じ型同士の計算は、その型になるというのが基本です。intとintの計算結果はintになります。したがって、3/2の結果はintになるのですが、小数部分は切り捨てられて1になるということです。なお、この場合は切捨てであって四捨五入ではありません。 5/3の結果はしたがって1になります。これも規則ですね。

一方、異なる型同士の計算は、より精度が高い(言い換えれば値として記録可能な範囲が広い)ものに結果はなります。言い換えれば、精度が低い方が高いほうの型に変換されて、計算が行われるということです。floatとintの計算結果は、floatになります。また、doubleとintの計算結果は、doubleになります。したがって、5/3は1だけど、5.0/3は1.6666666666666667となります。

キャストの優先順位は高い

キャストは、数値の前につけるマイナス記号のように、とにかく先に解釈されると思っておいて間違いはないでしょう。したがって、(float)5/3と、(float)(5/3)は違います。前者はintである5をfloatにするのでfloatとintの割り算となり3もfloatにしてしまって結果はfloatの1.666666となります。一方後者は、5÷3=1の結果をfloatに変換するので1.0になります。ただ、前者は、(float)5/(float)3のほうがある意味では明示的ではありますが、こちらの記述はややかったるいという気もするかもしれません。どちらがいいのかということは一概には言えませんが、いずれにしても意味はきちんと理解してください。

計算の途中経過にも注意を払う

テキストに説明してあるとおりです。(p71のコラム)

ところが、次のようなプログラムはきちんと、80+120-120の結果である80を表示します。

byte a=80, b=120;
System.out.println((a+b)-b);

これは、実は、(a+b)がint型になるという整数型特有のルールがあるのです。ほんとうなら、a+bの結果は200となりbyte型の範囲を超えるのですが、そこではいきなりintになるので、問題なく数値は扱えます。こうした例外的なものは随所にあったりするわけでかえってややこしいと思ってしまうところですが、例外はつきものです。原則とローカルルールをうまく理解すれば問題はありません。


練習問題4-1

奇数かどうかという判断は、2で割ったあまりが1かどうかで判断できる。つまり、1なら奇数、2なら偶数。以下のプログラム例は、「2で割ったあまりが0であるなら」という条件判断にしてみた。もちろん、このような、2通りにしかならないような判断の場合は、「2で割ったあまりが1でないなら」など何通りも条件判断の式は作成できることになる。基本的にはどれも正解である。

{
    System.out.println("********* Ex 4-1");

    int number = 11;

    if(number % 2 == 0)
        System.out.println(number + "は偶数です");
    else
        System.out.println(number + "は奇数です");
}

練習問題4-2

ちょっと回りくどい解説になってしまっているが、とにかく指定通りにプログラムを組んでみること。このとき、ある場所での判断結果を、それより後の別の場面で使いたいような場合、そのつど判断をするのでもかまわないが、問題が発生する恐れがある。1つにはプログラムの修正を行ったとき、同じ判断のある場所すべてに修正が必要になるかもしれない。また、判断処理が非常に時間がかかるような場合には、効率が悪くなる。そこで、ある段階での判断結果を、変数に記憶させておき、その後はいちいち条件判断するのではなく、変数の値を参照して判断結果を知るという手法がある。こうしたテクニックを一般的にフラグと言うが、少し複雑な処理を組む場合には知っておく必要があるものである。

{
    System.out.println("********* Ex 4-2");

    int number = 25;

    boolean flag = false;

    if(number % 2 != 0) {
        if(number % 5 != 0) {
            flag = true;
        }
    }
    if(flag)
        System.out.println("びんごー!");
    else
        System.out.println("はずれー");
}

練習問題4-3

前の設問は、フラグ変数を説明するための強引なサンプルでした。本当は、この程度の条件判断ならば、2つまとめて行ってもかまわない。こうした2つの条件が両方満たされた場合・・・といったような判断をするときに使うのが&&演算子である。

{
    System.out.println("********* Ex 4-3");

    int number = 25;
    if((number % 2 != 0) && (number % 5 != 0))
        System.out.println("びんごー!");
    else
        System.out.println("はずれー");
}

練習問題4-4

素数かどうかの判断方法は、いろいろ考えるところが多い。もちろん、人間が判断する方法をそのまま利用してもいいが、場合によっては、プログラムがやたら複雑になる。コンピュータは、単純な処理を高速にするという特徴があり、多くのプログラムではそうした力技を使うことはよくある。

素数かどうかの判断でも、常識としては「その数の半分よりも小さな素数で割り切れるものがあれば、その数は素数ではない」というのが基本になる。だけど、これをプログラミングするのは難しい(挑戦してもいいですよ)。むしろ、2〜その数の半分までの整数について、割り切れるかどうかを判断するということの法がプログラムは作りやすくなる。もちろん、2で割り切れないのに、4で割り切れるはずはないなどの無駄もあるが、状況によっては容認してもいいだろう。

こうした、繰り返しを行うときには、forを使う。forの使い方のポイントを知って欲しいので、ちょっと難しいプログラムではあるのだが、じっくり吟味してほしい。forの繰り返しの中で、変数がどう変化するのかを追うために、以下のプログラムでは、途中の変数の値を標準出力に随時出力してみた。

{
    System.out.println("********* Ex 4-4");

    int number = 23;
    boolean flag = true;
    for(int j=2; j < number/2 ; j++) {
        System.out.println("Number="+number+", j="+j+", number % j="+(number%j));
        if(number % j == 0)    {
            flag = false;
            break;        //テキストでは詳しく書いていないが、これにより、
                            //forの繰り返しを強制的に終わらせることができる
        }
    }
    if(flag == true)
        System.out.println(number + "は素数です");
    else
        System.out.println(number + "は素数ではありません");
}

breakについては、説明はありませんが、繰り返しを途中で終わらせるものです。これがある場合、ない場合でどんな結果の違いになるかを実際に確かめてみましょう。

それから、{ }の使い方はなれないとわかりにくいかもしれませんが、たとえば、ifの中の命令が1つだけなら{ }はあってもなくてもかまいません。2つ以上だと、{ }は必ず必要です。後者のように必ず必要な場合が出てくることもあるので、1つだけの命令の場合でも、{ }をつけておくことはよくあります。後から命令を加えたのに、{ }を忘れることってよくあるので、そうした予防線でもあります。