はじめMath! Javaでコンピュータ数学

第47回確率の数学 確率とは [後編]

前回は確率の数学の言葉を一通りおさらいしました。今回はその確認に役立つ道具となるシミュレーション用ソフトウエアを作りましょう。そして完成したものを利用して、実験で得られる値と確率の数学で得られる値との間に差があるかどうかを確認してみましょう。

問題1 さいころの働きをするクラスを作りましょう

今後確率の学習を進める中で、きっと役に立つプログラムを作っておきましょう。確率の数学といえば、やっぱりさいころです。

以下に肝心の部分を省略した未完成版のコードを掲載します。コンパイル・実行は可能ですが、Diceクラスの実装が不完全ですので目的の動作をしません。Diceクラスにコードを追加して完成させてください。

さいころは任意の面数のものを作成できるようにしてください。負の面数や、1面ダイスには意味がありません。2面以上のダイスが作成できるようにしましょう。さいころの目の合計がオーバーフローしないように気を配ってみてください。

ソースコード:Q Sample DiceRoll.java 未完成版
//サンプルコード
//さいころクラスを作ろう
//filename : Q_Sample_DiceRoll.java

import java.util.Random;

class Q_Sample_DiceRoll {

  public static void main(String[] args) {

    //4 面、6 面、20 面のダイス
    Dice d4 = new Dice(4);
    Dice d6 = new Dice(6);
    Dice d20 = new Dice(20);
    //不適切なダイス。roll を呼ぶと0 を返すはず。
    Dice d0 = new Dice(0);
    Dice dError = new Dice(Integer.MAX_VALUE);

    //それぞれ1 回、2 回、3 回振った結果を表示する。
    System.out.println("Dice roll test");
    System.out.println("正常に動作する場合");
    for (int i=1; i <= 3; ++i){
      System.out.println(i + "回振ったら"
                         + d4.roll(i) + "," + d6.roll(i)
                         + "," + d20.roll(i));
    }
    System.out.println("正常に動作しない場合");
    System.out.println(d0.roll(1));
    System.out.println(dError.roll(1));
    System.out.println(d6.roll(Integer.MAX_VALUE/6 + 1));
    System.out.println(d6.roll(-5));
    System.out.println("さいころの面数を確認");
    System.out.println(d4.getSize());
    System.out.println(d6.getSize());
    System.out.println(dError.getSize());
    System.out.println(d0.getSize());

  }// end of main


}// end of class Q_Sample_DiceRoll



/*
 * class Dice
 * 目的: さいころをシミュレートするクラス
 *      コンストラクタに整数値を指定して、
 *      さいころの面数を指示できる。
 */
class Dice {
/*
 * 目的: コンストラクタ
 * 引数: s ダイスの面数
 *      不適当な値なら、面数0 のダイスとなる。
 */
  Dice(int s){

  }// end of Constructor Dice(s)


/*
 * 目的: ダイスを振る
 * 引数: n ダイスを振る回数
 * 戻り値: 出たダイスの目の合計
 */
  public int roll(int n){
    return 0;
  }// end of roll


/*
 * 目的: ダイスの面数を返す
 * 戻り値: ダイスの面数
 */
  public int getSize(){
    return 0;
  }// end of getSize


} // end of class Dice

問題2 さいころを振って確率の値を確かめましょう

2つのことを確かめましょう。

(1)6面のさいころをなるべく多くの回数振って、それぞれの目ごとに(出現回数)/(試行回数)を計算しましょう。

(2)6面のさいころを2回振って、出た目の合計が3になる場合と5になる場合の数それぞれについて(出現回数)/(試行回数)を計算しましょう。なるべく多くの回数試行してください。

(1)を実行すると、さいころを振った数の6分の1に近い数になるはずです。⁠2)を実行すると3になる場合より5の出る場合の方が倍近く多いはずです。理論的に計算した確率の値と、コンピュータ・シミュレーションによる実験結果がどれほど一致するのか、それともしないのか、確かめてみましょう。

解説

問題1 さいころの働きをするクラスを作りましょう

完成したDiceクラスのみを示します。このクラスがあれば、さいころを使った確率の実験やゲームに役立つことでしょう。

大変シンプルなクラスですので、クラスを作るのが初めてだ、という人にもチャレンジしやすい問題だったことでしょう。このDiceクラスは、ファイルDice.javaとして保存し、今後の演習問題で活用できるよう保管しておいてください。

ソースコード:Sample DiceRoll.java 完成版(部分)
/*
 * class Dice
 * 目的: さいころをシミュレートするクラス
 *      コンストラクタに整数値を指定して、
 *      さいころの面数を指示できる。
 */
class Dice {

    //プライベートな変数・オブジェクトの宣言
    private int size = 6; // default
    private Random rd = new Random();

/*
 * 目的: コンストラクタ
 * 引数: s ダイスの面数
 *      不適当な値なら、面数0 とする。
 */
  Dice(int s){
    if ((s > 1) && (s < (Integer.MAX_VALUE / s))) {
      size = s;
    } else {
      size = 0;
    }
  }// end of Constructor Dice(s)


/*
 * 目的: ダイスを振る
 * 引数: n ダイスを振る回数
 * 戻り値: 出たダイスの目の合計
 */
  public int roll(int n){
    int sum = 0;
    if ((size != 0) && (n < (Integer.MAX_VALUE/size))
                    && (n > 0) ){
      for (int i=0; i<n; ++i){
        sum += rd.nextInt(size) + 1;
      }
    }
    return sum;
  }// end of roll


/*
 * 目的: ダイスの面数を返す
 * 戻り値: ダイスの面数
 */
  public int getSize(){
    return size;
  }// end of getSize


} // end of class Dice

問題2 さいころを振って確率の値を確かめましょう。

問題1で作成したDiceクラスをひとつのファイルとして保存しましょう。ファイル名はクラス名+.javaにしなければなりませんから、Dice.javaとします。保存した後、次のようにコマンドラインでコンパイルします。

> javac Dice.java

コンパイルを実行したディレクトリ内にDice.classが作成されます。その後、同じディレクトリで、確率の計算を実行するプログラムをコンパイルすると良いでしょう。以下にそのプログラム例 Sample_TestProbabilityByDiceRoll.javaを示します。

ソースコード:Dice.java
import java.util.Random;

/*
 * class Dice
 * 目的: さいころをシミュレートするクラス
 *      コンストラクタに整数値を指定して、
 *      さいころの面数を指示できる。
 */
class Dice {

    //プライベートな変数・オブジェクトの宣言
    private int size = 6; // default
    private Random rd = new Random();

/*
 * 目的: コンストラクタ
 * 引数: s ダイスの面数
 *      不適当な値なら、面数0 とする。
 */
  Dice(int s){
    if ((s > 1) && (s < (Integer.MAX_VALUE / s))) {
      size = s;
    } else {
      size = 0;
    }
  }// end of Constructor Dice(s)


/*
 * 目的: ダイスを振る
 * 引数: n ダイスを振る回数
 * 戻り値: 出たダイスの目の合計
 */
  public int roll(int n){
    int sum = 0;
    if ((size != 0) && (n < (Integer.MAX_VALUE/size))
                    && (n > 0) ){
      for (int i=0; i<n; ++i){
        sum += rd.nextInt(size) + 1;
      }
    }
    return sum;
  }// end of roll


/*
 * 目的: ダイスの面数を返す
 * 戻り値: ダイスの面数
 */
  public int getSize(){
    return size;
  }// end of getSize


} // end of class Dice
ソースコード:Sample TestProbabilityByDiceRoll.java
//サンプルコード
//..「1 の目の出る確率と、2 の目の出る確率が同じことを確認する。」
//..「2 回さいころを振って出た目の合計が、3 であるときと5 である
//  ときで確率が異なることを確認する。」
//filename : Sample_TestProbabilityByDiceRoll.java

class Sample_TestProbabilityByDiceRoll {

  public static void main(String[] args) {

    Dice d6 = new Dice(6);
    int REPEAT = 1000000;
    int ROH1 = REPEAT / d6.getSize();

    //---------------------------------
    System.out.println("6 面のダイスを1000 回振り、"
             + "それぞれの目が出た数を表示する。");
    int num[] = new int[6];
    for (int i=0; i<REPEAT; ++i){
      ++num[d6.roll(1)-1];
    }
    for (int i=0; i<6; ++i)
      System.out.println(i+1+"の目は"+num[i]+"回出ました");
    System.out.println("試行回数は"+REPEAT+"、場合の数は"+ROH1+"程度になるべきです。");

    //---------------------------------
    System.out.println("2 回ダイスを振って出た目の合計が"
                             + "3 か5 かの場合を数える。");
    int num3 = 0;
    int num5 = 0;
    int result;
    for (int i=0; i<REPEAT; ++i){
      result = d6.roll(2);
      if(result == 3){
        ++num3;
      } else if(result == 5) {
        ++num5;
      }
    }
    System.out.println("3 になった回数は"+num3);
    System.out.println("5 になった回数は"+num5);
    System.out.println("後者は前者の2 倍になるべきです。");
    System.out.println("後者は前者の"+((float)num5/(float)num3)+"倍になりました。");
  }// end of main


}// end of class Sample_DiceRoll

シミュレーションの結果得られた(目的の場合の数)/(試行回数)の値が、確率の数学で求めた値と近ければ、確率の数学は実際的な事柄に適用しても有意義であると言えます。確率の数学知識は、膨大な数の試行を行わなくとも、理論的に結果を推測できるのですから、役にたちます。

シミュレーションは試行回数が多ければ多いほど、確率の計算で求めた値と近くなるはずです。試行の数が多ければ多いほど理論計算値と試行の結果が近くなる、という状況を、確率の数学では大数の法則[1]といいます。逆にとらえれば、試行回数が少ない場合は確率の数学はあてにならないと言えます。

試行回数が十分に大きいのに、確率の数学を用いた値と、シミュレーション結果との間に大きな違いがあるならば、確率の数学の考え方、あるいは問題のとらえ方に問題があるかもしれません。逆に、シミュレーションの手順にどこか問題があるのかもしれません。基本に立ち戻って、原因の究明に努めてみてください。問題が起こったときこそが、最高の学習の機会です。

おすすめ記事

記事・ニュース一覧