Liukulukulaskenta Pekka Hotokka pejuhoto@cc.jyu.fi 10.11.2004 Tiivistelmä Liukulukuja tarvitaan, kun joudutaan esittämään reaalilukuja tietokoneella. Niiden esittämistavasta johtuen syntyy laskennassa helposti virheitä, jotka saattavat johtaa epätarkkaan lopputulokseen. Ohjelmakoodin kirjoittajakin pystyy omalta osaltaan vaikuttamaan tulosten tarkkuuteen. 1 Johdantoa liukulukuihin Joitakin matemaattisia ongelmia, esimerkiksi epälineaarisia yhtälöitä ja suuria yhtälöryhmiä, on mahdoton tai liian työläs ratkaista analyyttisen tarkasti, joten niiden numeeriseen ratkaisemiseen käytetään yleensä tietokoneita. Tämän takia tarvitaan esitystapa, jolla reaalilukuja voidaan esittää ja tallentaa tietokoneympäristössä. Heti tästä seuraa ensimmäinen ongelma; koska reaalilukuja on ääretön määrä, niin ei niitä voida mitenkään esittää yksikäsitteisesti äärellisellä määrällä bittejä, vaan lukuja joudutaan pyöristämään ja katkaisemaan. Nykyään tietokonekäytössä käytettävää reaalilukujen esitystapaa kutsutaan liukulukuesitykseksi ja tässä raportissa kerrotaan liukulukuesityksen perusteista, standardeista ja liukulaskennassa syntyvistä virheistä sekä niiden minimoimisesta. 2 Liukulukuesityksen formaatti Liukuluvun yleinen esitysformaatti sisältää etumerkin, merkitsevän desimaaliosan eli mantissan ja eksponentin. Mantissa esitetään kantaluvun, yleensä 2 tai 10, potenssien p (p = 0, 1, 2...) summana, joka skaalataan sopivalla eksponentilla. Yleensä mantissa esitetään normalisoidussa muodossa, 1
siten että mantissan ensimmäinen luku ei ole 0. Tästä on se hyöty, että liukuluku on tällöin yksikäsitteinen, eli yhdelle reaaliluvulle ei ole useampaa liukulukuesitystä [1]. Tietokonekäytössä luonnollisin kantaluku on 2, jolloin potenssien esittämiseen riittää yksi bitti. Tosin esimerkiksi desimaalijärjestelmän luku 0.1 tuottaa binääriesityksenä päättymättömän bittijonon, joka on katkaistava tai pyöristettävä lähimpään esitettävissä olevaan lukuun, kun mantissan esittämiseen tarkoitetut bitit loppuvat kesken. Koska myös eksponentilla on rajattu määrä arvoja, voi itseisarvoltaan erittäin pieni tai suuri luku olla lukualueen ulkopuolella, jolloin sitä ei voida esittää liukulukumuodossa [1]. Liukuluvun binääriesityksessä etumerkki vie yhden bitin ja loput jaetaan mantissan ja eksponentin kesken, esimerkiksi 32-bittisessä liukuluvussa on mantissan osuus 23 bittiä ja eksponentin osuus 8 bittiä [3]. 3 IEEE-standardit IEEE 754 on binäärinen liukulukustandardi, joka määrittää muunmuassa neljä vaihtoehtoa luvun esitystarkkuudelle, sekä joitakin erikoistapauksia ja laskuoperaatioita liukuluvuille [1]. Luvussa 2 mainittu 32-bittinen muoto on yksinkertaisen tarkkuuden liukuluku, joka pystyy esittämään reaaliluvun noin seitsemän desimaalin tarkkuudella eksponentin ollessa 10 38... 10 38. Toinen muoto on 64-bittinen kaksinkertaisen tarkkuuden liukuluku, jolla saavutetaan likimain 15 desimaalin tarkkuus eksponentin ollessa välillä 10 307... 10 307 [2]. Lisäksi on olemassa laajennetut yksin- ja kaksinkertaisen tarkkuuden luvut, joille on määritelty ainoastaan halutun tarkkuuden ja vaihteluvälin alarajat. Esimerkiksi 80-bittinen laajennetun kaksoistarkkuuden esitys on käytössa Intelin x86-prosessorisarjan liukulukuyksikössä ja monissa laskimissa [1]. Standardissa erikseen määriteltyjä erikoistuloksia ovat ±, joka on tuloksena esimerkiksi laskusta ±1/0, sekä epäluku (Not a Number, NaN), joka saadaan tuloksena muunmuassa laskusta 1 [1]. 4 Virheet laskennassa Ensimmäinen mahdollisuus virheen syntymiseen on reaaliluvun muuttamisessa binääriesitykseksi. Suurinta mahdollista pyöristysvirhettä kutsutaan konevakioksi, ja sitä merkitään ɛ kone. Virheen suuruus riippuu käytetyn esityksen tarkkuudesta ja esimerkiksi 32 bittisille liukuluvulle se on n. 10 7 [3]. 2
IEEE:n standardi vaatii, että peruslaskutoimitukset lasketaan tarkoilla arvoilla ja vasta lopputulos pyöristetään käytettävän liukulukutarkkuuden mukaan. Tämä on kuitenkin erittäin kallis operaatio, jos lukujen eksponentit eroavat paljon toisistaan, koska laskemista varten lukujen eksponentit on muutettava samoiksi ja toista lukua joudutaan täyttämään nollilla. Loppujen lopuksi saatetaan päätyä suorittamaan laskenta useilla kymmenillä ylimääräisillä numeroilla [1]. Nopeampi tapa suorittaa laskutoimitukset on muuttaa lähtöarvojen eksponentit samoiksi kuten yllä, mutta sitten hylätä kaikki käytetyn tarkkuuden ulkopuolelle jäävät numerot. Tällä menetelmällä saattaa kuitenkin syntyä hyvin suuria virheitä, esimerkkinä lasku 10.1 9.93, jonka tarkka vastaus on 0.17. Kun käytetään kolmen desimaalin tarkuutta, niin x = 1.01 10 1 ja y = 0.99 10 1 ja tuloksessa x y = 0.02 10 1 ei ole yhtään desimaalia oikein [1]. Koska kumpikaan ylläolevista laskentatavoista ei ole kovin käyttökelpoinen, käytetään yleensä näiden välimuotoa. Laskutoimitukset suoritetaan yhdellä tai useammalla ylimääräisellä numerolla esitystarkkuuteen verrattuna, jolloin laskuvirheistä kärsineet ylimääräiset numerot eivät näy lopputuloksessa. Kun ylimääräisiä desimaaleja on tarpeeksi on lopputulos sama kuin IEEE:n mukaisella laskutavalla, joten standardin täyttävät tulokset on laskettavissa suhteellisen tehokkaasti [1]. 5 Virheherkät operaatiot ja niiden välttäminen Vaikka laskut laskettaisiin tarkasti, tulee niihin silti aina pyöristysvirheitä. Yhteenlaskussa pahimmat virheet tapahtuvat, jos summattavien lukujen suuruusluokat eroavat paljon toisistaan. Pahimmassa tapauksessa pienempi luku ei vaikuta lopputulokseen ollenkaan. Tarkemman tuloksen saamiseksi voi yrittää skaalata lukuja samaan suurusluokkaan, jos se on mahdollista. Erityisesti pitkiä sarjojen summia laskettaessa on hyvä ottaa huomoon laskujärjestys. Jos summataan paljon pieniä lukuja isoon summaan, saattaa lopputulos vääristyä pahasti, mutta jos summaus aloitetaankin pienimmistä luvuista, niin saadaan huomattavasti tarkempi tulos [3]. Vähennyslaskuissa suurimmat virheet syntyvät kahden lähes yhtäsuuren luvun vähentämisessä, tällöin tulokseen saattaa jäädä hyvin pieni määrä merkitseviä numeroita. Yksittäisessä vähennyslaskussa tämän välttäminen on hankalaa, mutta jos kyse on laajemmasta matemaattisesta kaavasta, niin voidaan sitä yleensä muokata siten, että kyseistä vähennyslaskua ei siinä esiinny [3] [2]. Kertolaskuissa on tärkeää, ettei itseisarvoltaan hyvin suuria tai pieniä 3
lukuja kerrota keskenään. Tällöin vaarana on että mennään käytetyn lukualueen ulkopuolelle ja seurauksena on ali- tai ylivuototilanne. Alivuoto on yleensä vähemmän vaarallinen tilanne, koska tulos ainoastaan pyöristetään nollaksi, mutta ylivuodon tuloksena on ääretön, josta seuraa helposti virhetilanne. Jakolaskussa vastaava tilanne syntyy kun jaetaan itseisarvoltaan suurta lukua pienellä tai pientä suurella. [3] 6 Muita huomioitavia asioita Vertailuoperaattoreista yhtäsuuruuden käyttäminen on varsin riskialtista, koska laskutoimitusten tuloksia ei välttämättä voida esittää tarkasti ja saman lausekkeen erilainen laskujärjestys saattaa tuottaa erilaisen tuloksen. Parempi tapa yhtäsuuruuden x == y vertailuun olisikin x y < δ, missä δ on sopiva (pieni) toleranssiparametri [2]. Riittävän laskentatarkkuuden saavuttaminen riippuu hyvin paljon myös käytetystä liukuluvun esitystarkkuudesta. Jos käytössä on 32- ja 64-bittiset liukuluvut, kannattaa nykyisillä muistimäärillä ja prosessoritehoilla käyttää suurempaa tarkkuutta ellei ole tarvetta säästää muistia tai ole varmuutta siitä, että kyseisessä tapauksessa yksinkertainen tarkkuus on riittävä. 7 Yhteenveto Liukulukuja tietokoneella käytettäessä on aina syytä ottaa huomioon, että niiden käsittely eroaa huomattavasti tavallisten kokonaislukujen käsittelystä. Suurta tarkkuutta vaativissa tapauksissa on monta asiaa, mitä pitää ottaa huomioon, koska yksikin epätarkka välitulos tai lähtöarvo voi pilata koko laskun lopputuloksen. Vaikka suuri osa liukulukulaskuihin liittyvistä asioista onkin laitetason ja kääntäjän huolia, myös ohjelmakoodin kirjoittaja pystyy vaikuttamaan siihen, kuinka hyviä tuloksia saadaan. Viitteet [1] D. Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic, ACM Computing Surveys, Vol 23, No 1, March 1991. [2] Raino A. E. Mäkinen: Numeeriset menetelmät, Luentomoniste, Jyväskylän yliopisto 2003, Liite A. 4
[3] J. Haataja, J. Heikonen, Y. Leino, J. Rahola, J. Ruokolainen ja V. Savolainen, Numeeriset menetelmät käytännössä, CSC Tieteellinen laskenta Oy 2002, s. 393399. 5