Tämän esimerkin pyöristysvirheestä syntyi keskustelua kurssin uutisryhmässä joskus 90-luvun lopulla. Koska keskustelu saattaa valaista asiaa, kopioin sen osia tännekin. Tämä asia ei kuitenkaan kuulu kurssin varsinaiseen sisältöön. AW ============================================================ Yksi opiskelija kirjoitti: ------------------------------------------------------------ Java-materiaalin osassa 2.4 (Loogisia lausekkeita, valintaa ja toistoa) on esimerkki jossa tulostetaan piin monikertoja. Samassa kerrotaan myös "pyöristysvirheestä", esimerkkinä 12.56+3.14=15.700000000000001. Miten tämä on mahdollista? Missä ympäristöissä esiintyy? Mikä on pyöristyksen logiikka (miksi vain jotkut arvot pyöristyvät)? Mitä käsite "tietokoneen esitystapa" kattaa? ------------------------------------------------------------ Vastasin: ------------------------------------------------------------ Mitenkähän tätä nyt lyhyesti selittäisi ... Kaikki tiedot koneessa esitetään äärellisen pituisina bittien jonoina, niin myös liukuluvut. Esitysmuoto on sellainen, että luvun "merkitsevät numerot" esitetään ns. mantissana ja luvun suuruusluokka ns. eksponenttina ... No oleellista on joka tapauksessa, että reaaliluvuista vain osa voidaan esittää. Ja esitysmuodon lukuparin molemmat luvut ovat binäärilukuja. Noin esitetyt luvut eivät välttämättä satu samoihin kohtiin kuin vaikkapa kaksidesimaaliset 10-järjestelmän luvut. Javassa käytetyssä liukulukujen esitysmuodossa luvun 12.56 paras likiarvo ynnättynä luvun 3.14 parhaaseen likiarvoon tuottaa sellaisen liukuluvun, jonka esitys 10-järjestelmässä onkin tuo 15.70000....1 Edellinen on ainakin yhdessä kohdin epätarkka selitys, mutta ehkä kuitenkin vähän lohduttaa... Joku voi esittää teknisemmän selityksen? ------------------------------------------------------------ Tommi Teräsvirta täydensi: ------------------------------------------------------------ Yritänpä: Asiat esitetään kyllä Tietokoneen toiminta kurssillakin, mutta kaikki Ohjelmoinnin perusteita opettelevat eivät ehkä kyseistä kurssia ole vielä käyneet. Osa selityksestä saattaa tuntua rautalangan vääntämiseltä, mutta koittakaa kestää. Tuli hieman hätäisesti kirjoitettua. Tietokoneessahan kaikki luvut esitetään binaarisessa muodossa. Esimerkiksi kokonaisluku 23 on binäärimuodossa 10111, eli 2^4 + 2^2 + 2^1 + 2^0. Liukuluvut esitetään vastaavasti, joten kymmenkantainen luku 5.75 on 101.11, eli 2^2 + 2^0 + 2^-1 + 2^-2 (siis 4+1 + 0.5 + 0.25). Kaikki kymmenjärjestelmän luvut muutetaan siis biteiksi ja laskut tapahtuvat siinä muodossa. Jos nyt mietit mitä on 3.14 bittimuodossa esitettynä, niin saat miettiä aika pitkään. Kokonaislukuosa on helppo 11, mutta desimaaliosa onkin hieman pidempi, tässä likiarvo luvulle 3.14: 11.001000111101011100001010001111010111000010100011111 Myös luku 12.56 sattuu olemaan likiarvo, nimittäin: 1100.1000111101011100001010001111010111000010100011111 Kun nämä ykköset ja nollat lasketaan yhteen, niin saadaan luku 1111.1011001100110011001100110011001100110011001100111 Tuo taas ei ole aivan sama kuin jos muuttaisit luvun 15.7 biteiksi: 1111.1011001100110011001100110011001100110011001100110 Eli siellä on se yhden bitin ero. Kannattaa siis aina ohjelmoitaessa, myös muilla kielillä kuin Javalla, käsitellä liukulukuja likiarvoina. Hieman teknistä tietoa: Javan double on IEEE 754 standardin mukaisessa muodossa oleva kaksinkertaisen tarkkuuden liukuluku, siis 64 bittiä pitkä. float taas on saman standardin yksinkertaisen tarkkuden liukuluku, 32 bittiä pitkä. double 64 bittiä kertovat seuraavaa: bitti numero 63 kertoo luvun etumerkin, bitit 62-52 eksponentin ja bitit 51-0 kertovat mantissan. (Lähde: The Java(tm) Language Specification §20.10.21) Seuraavalla koodinpätkällä saa selville miltä luvun 3.14 bittijono näyttää: long l = Double.doubleToLongBits(3.14); System.out.println("3.14 bitteinä:\n"+ Long.toBinaryString(l)); Se tulostaa: 3.14 bitteinä: 100000000001001000111101011100001010001111010111000010100011111 Jos joku yrittää tuota muuttaa kymmenjärjestelmään, niin kannattaa muistaa, että IEEE standardin 754 mukaan ensimmäinen merkitsevä bitti jätetään pois (piilobitti), näin saadaan mantissaan yksi merkitsevä bitti lisää. ------------------------------------------------------------ Sitten tuli vielä lisämurhe Javan versioiden erilaisesta käyttäytymisestä. Vastasin yhdelle opiskelijalle: ------------------------------------------------------------ >: >12.56+3.14=15.700000000000001. >... Yritin tuottaa >virheen uudelleen, mutta en onnistunut (kontti): >12.56 >15.7 Kontissa siis lie versio 1.0.2? Kokeilin itsekin laitoksen vanhassa versiossa ja siellä kävi samoin. Muutenkin 1.0.2 _tulostaa_ liukuluvut hieman eri tavalla kuin 1.1.3. Semmoinen tämä maailma on, epätäydellinen ... ------------------------------------------------------------ Yksi opiskelija vielä jatkoi: ------------------------------------------------------------ ...ja täydellinen maailma ei olisi täydellinen koska siitä puuttuisi epätäydellisyys... ------------------------------------------------------------