Kokonaisluvuilla (tyyppi int
) laskiessaan Python laskee tarkasti,
jos koneen muisti vain riittää.
Esimerkiksi luvun $2^{10000}$ laskeminen komennolla 2**10000
ei tuota ongelmia.
Desimaalilukujen kanssa ollaan kuitenkin pulassa, sillä esimerkiksi
$\sqrt{2}=1{,}4142...$ sisältää jo äärettömästi desimaaleja, eivätkä ne voi mahtua koneen
äärelliseen muistiin.
Tyypillinen tapa tallentaa desimaalilukuja tietokoneella on liukuluku
(eng. floating point number, tyyppi float
).
Liukuluvussa desimaalipilkku tulee ensimmäisen merkitsevän numeron jälkeen,
eli esimerkiksi luku $12345$ merkittäisiin
$$12345 = 1{,}2345\cdot 10^4.$$
Kerrointa $1{,}2345$ kutsutaan nimellä mantissa.
Tietokoneen rajat tulevat vastaan siinä, että mantissassa on käytössä vain rajallinen määrä merkitseviä numeroita.
Siksi esimerkiksi luku
$$1/3 = 0{,}33333{...} = 3{,}3333{...} \cdot 10^{-1}$$
ei voi tallentua tietokoneelle tarkkana, vaan se pyöristetään lähimpään käytössä olevaan lukuun.
Lisäksi käytössä on vain rajallisen kokoisia eksponentteja, joten mielivaltaisen suuria lukuja ei voi esittää.
Itse asiassa Python tallentaa luvut 2-järjestelmässä eli binäärilukuina. Käytössä on 53 merkitsevää numeroa 2-järjestelmässä, mikä tarkoittaa 10-järjestelmässä noin $15$ merkitsevää numeroa. Suurin mahdollinen kerroin on $2^{2023}$ ja pienin $2^{-2022}$. Tästä seuraa, että tarkkana tallentuvat vain ne luvut, joiden esitys binäärilukuna on päättyvä. Esimerkiksi kymmenjärjestelmän luvun $0{,}2$ binääriesitys on päättymätön: $0{,}0011001100110011{...}$ Kun tämän pyöristää $53$ merkitsevään numeroon binäärijärjestelmässä, tuloksena on luku, joka vastaa kymmenjärjestelmässä lukua $$0{,}20000000000000001110223024625156{...}$$
Oleellisinta tässä kaikessa on siis se, että desimaaliluvuilla laskettaessa kaikki desimaalit 16. merkitsevästä numerosta lähtien ovat täysin epäluotettavia.
Liukuluvuista Pythonissa löydät lisätietoja Pythonin dokumentaatiosta ja Pythonin käyttämästä liukulukustandardista Wikipediasta.
==
on huono idea.
Kirjoita ohjelma, joka tulostaa peräkkäisille riveille lausekkeiden
0.1 + 0.1 == 0.2
0.1 + 0.2 == 0.3
0.3 + 0.5 == 0.8
3*0.1 == 0.3
0.1 + 0.1 - 0.2
0.1 + 0.2 - 0.3
0.3 + 0.5 - 0.8
3*0.1 - 0.3
Kirjoita ohjelma tähän:
==
.
Parempi tapa lukujen $a$ ja $b$ yhtäsuuruuden testaamiseen on tyytyä tarkistamaan,
että $|a-b|$ on riittävän pieni, missä "riittävän pieni" tarkoittaa
"monta kokoluokkaa pienempi kuin suurempi luvuista $|a|$ ja $|b|$".
Esimerkiksi voitaisiin tarkistaa, onko $x = 2{,}2360689774$ hyvä likiarvo luvulle
$\sqrt{5}$ tutkimalla, päteekö $\left|x-\sqrt{5}\right|<10^{-9}$.
Tee koodi, joka tulostaa Likiarvo on oikein!
tai Likiarvo on väärin!
sen mukaan,
toteutuuko tämä ehto vai ei.
Neliöjuuren saa math
-paketin funktiolla math.sqrt
ja itseisarvon funktiolla abs
.
Kirjoita ohjelma tähän:
10.0**50 + 1 - 10.0**50
antaa tulokseksi 0.
Syynä on se, että $10^{50}+1 \approx 10^{50}$, ellei käytössä ole 51 merkitsevää numeroa.
Kirjoita koodi, joka tulostaa peräkkäisille riveille lausekkeen
10.0**n+1-10.0**n
arvon, kun
$n = 1, 2, 3, \ldots, 20$. Muista kirjoittaa 10.0
, ei 10
. Mitä huomaat?
Kirjoita ohjelma tähän:
Ongelma johtuu siitä, että $b^2$ on paljon suurempi kuin $4ac$. Tällöin pätee $$\sqrt{b^2-4ac} \approx \sqrt{b^2-0} = |b|.$$ Kun merkitsevät numerot loppuvat kesken, lukujen $\sqrt{b^2-4ac}$ ja $b$ likiarvo voi siis olla sama! Ratkaisukaava päätyy muotoon $$x_1\approx \frac{-b + |b|}{2a} \quad \text{ja} \quad x_2\approx \frac{-b - |b|}{2a},$$ jolloin positiivisilla luvuilla $b$ saadaan $x_1\approx 0$ ja vastaavasti negatiivisella luvulla $b$ saadaan $x_2 \approx 0$, eikä vastauksessa ei ole yhtään merkitsevää numeroa oikein. Vähennyslasku on vaarallinen!
Koodin tulee toimia myös yhtälöille, joissa $b^2$ on paljon suurempi kuin $4ac$, esimerkiksi edellä esitetylle yhtälölle $3x^2+10^{20}x+5=0$ sekä yhtälölle $3x^2-10^{20}x+5=0$.
Voit halutessasi toteuttaa myös tarkistuksen sille, onko reaalisia ratkaisuja ylipäänsä olemassa.
Tätä tehtävää ei arvostella automaattisesti.
Kirjoita ohjelma tähän: