Opettavaisia mokia 2. viikon tehtävässä 5.2

Arto Wikla 12.9.2011

Tehtävä on seuraavanlainen:

Java-kirjaston työkalu Math.random() palauttaa arvonaan double-tyyppisen satunnaisluvun puoliavoimelta väliltä [0, 1). Tämä tarkoittaa, että nollakin voi tulla, mutta tasan yksi ei enää ole mahdollinen. Muokkaa edellisen tehtävän ärsyttävän ylivoimaisesta kivi-paperi-sakset-pelistä reilu versio. Tietokone siis arpoo oman vastauksensa ja ilmoittaa tuloksen, joka voi nyt olla "Voitit!" ja "Voitin!"

Tehtävän virheellinen vihje kolmen vaihtoehdon arvontaan oli

if (Math.random() < 1/3)
  // 1. vaihtoehto
else if (Math.random() < 2/3)
  // 2. vaihtoehto
else
  // 3. vaihtoehto

Vihjeessä on kaksi mokaa, tyhmä ja kiinnostava:

  1. Kuten on opittu, kauttaviiva Javassa tarkoittaa kokonaisjakoa, jos sekä jaettava että jakaja ovat kokonaislukuja. Näin vihjeen ohjalma valitsisi aina kolmannen vaihtoehdon, koska Math.random() ei voi palauttaa nollaa pienempää lukua!

  2. Vaikka tuo virhe korjataan laittamalla jaettava (ja/tai jakaja) liukuluvuksi, vihje ei ole vieläkään kelvollinen, jos kaikki kolme vaihtoehtoa halutaan yhtä todennäköisiksi. Ensimmäisen vaihtoehdon todennäköisyys on toki 1/3, mutta toisen vaihtoehdon todennäköisyydeksi tulee (2/3)*(2/3)=4/9 ja viimeisellä jää (1/3)*(2/3)=2/9. Kun tällä tavoin korjattu ohjelmanpätkä suoritetaan 10000000 kertaa, saadaan seuraavanlaisia jakaumia:
    1.kokeilu:
    
    3333136
    4445754
    2221110
    
    2. kokeilu:
    
    3332384
    4444685
    2222931
    

Korjataan vihje sellaiseksi, jossa satunnaisluku arvotaan vain kerran:

double arvottuLuku = Math.random();
if (arvottuLuku < 1.0/3.0)
  // 1. vaihtoehto
else if (arvottuLuku < 2.0/3.0)
  // 2. vaihtoehto
else
  // 3. vaihtoehto

Jo alkaa 10000000-kertainen testikin tuottaa toivottuja tuloksia:

1.kokeilu:

3334630
3333997
3331373

2.kokeilu:

3333672
3332557
3333771