Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Python: Lukujen tarkkuus

Sivun loppuun

kyyhky89 [16.04.2019 09:54:38]

#

Hei kaikille. Minulla olisi tosi tärkeää saada ohjelmani kuntoon. Olen tehnyt sitä jo kolme kuukautta ja ihan loppumetreillä huomasin, että minulta on jäänyt jokin perusasia ymmärtämättä. Olen lukenut aiheesta paljon, mutta en olen saanut riittävän selkeää vastausta. Tässä se on yksinkertaisuudessaan.

print(1.1*7)

Tämän selkeämpää esimerkkiä en osaa antaa. Vastaus on 7.700000000000001. eikä suinkaan 7.7. Ohjelmani on erittäin monimutkainen ja desimaaleja on pakko käyttää. Kaikki luvut kertautuvat jatkuvasti ja jopa näin pienet desimaalit ratkaisevat. Olen kokoeillut monia pyöristysmenetelmiä, mutta olen ollut huomaavinani, että ne ovat lähinnä kosmeettisia. Siis vaikka saisin roundilla tai vastaavilla oikean vastauksen näytölle, niin siitä huolimatta ohjelma laskee näillä pitkillä desimaaleilla. Mitä ihmettä tekisin nyt. Ohjelmani joutaa romukoppaan, jos en voi luottaa laskutoimituksiin.

Kiitos kaikille, tiedän tästä aiheesta olleen paljon keskustelua, mutta en siitä huolimatta ole löytänyt ratkaisua. Vielä... En voi muuttaa lukuja kokonaisluvuiksi ja silti lukujen pitää olla täysin tarkkoja.

Kyyhky

Teuro [16.04.2019 10:08:01]

#

Desimaaliluvut eivät ole juuri koskaan täysin tarkkoja. Tähän sopiva ratkaisu vaikka kieli onkin eri.

Grez [16.04.2019 10:45:15]

#

Ehkä parempi olisi sanoa että liukuluvut eivät yleensä ole täysin tarkkoja, desimaaliluvut on monissa kielissä erikseen ja ne usein ovat tarkkoja tietyissä rajoissa.

Pythonilla tosiaan vakiotietotyyppi eikokonaisluvuille on float, joka on 64-bittinen liukuluku.

Jos tosiaan liukuluvuissa tulevat virheet kertautuvat, niin voisit käyttää Decimal tai Fractions luokkaa, joista ensimmäisessä voit määritellä tarvittavan tarkkuuden ja jälkimmäinen on tarkkoja arvoja jopa murtoluvuille. Kaikenlaisia muitakin matematiikkakirjastoja varmasti löytyy tarkoilla luvuilla laskemiseen. Kannattaa toki aina muistaa että mitä tarkempaan lasketaan, sitä enemmän suorituskykyä se vaatii.

https://www.programiz.com/python-programming/numbers#dec

Sinänsä tässä päästään sitten lopulta siihen, että jos tietäisi mitä asiaa ohjelmallasi lasketaan, pystyisi ehkä sanomaan miten se oikeastaan kannattaisi tehdä.

kyyhky89 kirjoitti:

Ohjelmani joutaa romukoppaan, jos en voi luottaa laskutoimituksiin.

Tietokonehan sinänsä laskee luotettavasti, mutta sinun täytyy tietää mitä käsket sen laskemaan, tai muuten tulos ei välttämättä ole sitä mitä oletat.

kyyhky89 [16.04.2019 11:18:55]

#

Hei. Voisiko joku vahvistaa seuraavan:

print(float("%.2f" % (1.1*7)))

Näytöllä lukema muodostuu nyt oikein(7.7), mutta onko tämä totta?
Minulle riittää tämä, jos voin olla varma tämän oikeellisuudesta, virheisiin ei ole varaa, siis että tässä olisi kuitenkin piilossa lukema 7.700000000000001.

Kyse on siis vain siitä, että kone laskee oikein 7 x 1.1 = 7.7

Grez [16.04.2019 11:25:59]

#

kyyhky89 kirjoitti:

Hei. Voisiko joku vahvistaa seuraavan:

print(float("%.2f" % (1.1*7)))

Näytöllä lukema muodostuu nyt oikein(7.7), mutta onko tämä totta?

Se on ihan totta, eli laskun tulos (7.700000000000001) pyöristettynä 2 desimaalin tarkkuudelle on 7,7.

kyyhky89 kirjoitti:

Kyse on siis vain siitä, että kone laskee oikein 7 x 1.1 = 7.7

Kone laskee aivan oikein mutta lukuina (välipyöristyksin) binäärinä.

Ihan samalla tavalla jos lasket 10 / 3 * 3 välipyöristyksin käsin, niin esim 5 desimaalin tarkkuudella saisit tuloksen:

10 / 3 * 3
= 0.33333 * 3
= 0.99999

Eli jos sinulla onkäytössä 5 merkitsevää numeroa kymmenjärjestelmässä, niin voit esittää luvun 10/3 joko
0.33333 tai
0.33334 ja ensimmäinen on lähempänä todellista arvoa.

Samalla tavalla tietokone voi tallentaa 64-bittiseen liukulukumuuttujaan luvun 11/10 joko
1.10000000000000008881784197001 tai
1.09999999999999986677323704498, joista ensimmäinen on lähempänä annettua arvoa

Eli tietokoneen sisäisesti käyttämällä 2-lukujärjestelmällä (binäärilukuina)
1011 / 1010 on joko
0011111111110001100110011001100110011001100110011001100110011010 tai
0011111111110001100110011001100110011001100110011001100110011001

Tarkka arvo jatkuisi ikuisesti, aivan kuten 10/3 tarkka arvo kymmenjärjestelmän desimaaliesityksenä jatkuu ikuisesti.

Tietokoneen voi toki käskeä laskemaan vaikka miljardin desimaalin tarkkuudella, jolloin virhe luultavasti ei sinunkaan laskutuoimuksissasi pääsisi hirveän pahasti kertautumaan. Se on sitten toinen asia että onko se järkevää (eli riittäisikö pienempikin tarkkuus tai eri tyylillä laskeminen)

kyyhky89 [16.04.2019 11:53:09]

#

Kiitos kaikille. Nyt siis tiedän, että lopullinen luku on 7.7. eikä se enää kertaannu! Kiitos fiksuista vastauksista.

kyyhkynen

Grez [16.04.2019 11:56:12]

#

Voit toki pyöristellä liukuluja halutulle määrälle 10-lukujärjestelmän desimaaleja joka välissä, mutta siinä tapauksessa* huomattavasti helpompaa ja järkevämpää olisi käyttää Decimal -luokkaa.

Kannattaa myös huomata, että 64-bittisessä liukuluvussa on vain 52-bittinen mantissa, eli pyöristysvirheet menee jopa desimaalipilkun vasemmalle puolelle kun luvut kasvaa suuremmiksi kuin 2^52 eli 4 503 599 627 370 496

* Eli siis jos sinulle riittää matala tarkkuus mutta et halua 2-lukujärjestelmämuunnoksista aiheutuvia pyöristysvirheitä.

The Alchemist [16.04.2019 12:02:48]

#

kyyhky89 kirjoitti:

Kiitos kaikille. Nyt siis tiedän, että lopullinen luku on 7.7. eikä se enää kertaannu! Kiitos fiksuista vastauksista.

Näytölle tulostettuna "lopullinen luku" on totta kai aina se arvo, mikä näytölle on tulostettu. Ruudulla olevat merkit eivät jälkikäteen vaihdu toisiksi itsestään. Mutta tuota arvoa et voi float-tyyppiseen muuttujaan tallentaa, koska se on täysin mahdotonta. Et siis vieläkään ole ymmärtänyt oikein vaan olet luultavasti ymmärtänyt entistä enemmän väärin.

Äläkä käytä tulosteen muotoiluun float-string-float-muunnosta, se on aivan hullua, vaan oikeita matemaattisia pyöristysfunktioita round, floor ja ceil. Tai sitten suoraan sitä merkkijonomuunnosta %-operaattorilla...

kyyhky89 [28.04.2019 10:30:32]

#

Hei kaikille. Olen siis yrittänyt melkein mitä vain, jotta saisin yksinkertaisia lukuja pyöristettyä oikein, mutta se ei vain millään onnistu. Olin jo niin onnellinen, kun luulin keksineeni miten pyöristyksen voi tehdä oikein, mutta ei se mennytkään niin:

from tkinter import *
print(float("%.2f" % (1.1*7)))

Nyt sain ohjelmani valmiiksi ja desimaalivirhehän sieltä jälleen löytyi. Tässä yksinkertainen esimerkki siitä, miten sen löytää:

from tkinter import *
vuositavoite=float("%.2f" % (41.5*1.75))
print("vuositavoite 72,625 = 72,63 ",vuositavoite)

Olen niin neuvoton, olen yrittänyt aivan kaikkea. Aina löytyy lopulta jokin laskutoimistus yksinkertaisilla lukemilla, mitkä saavat virheen aikaan. Onko nyt kuitenkin niin, että Python ei vain sovellu laskemiseen ja kannattaisi opetella jokin muu kieli? Mitä tulee tuohon edelliseen viestiin, niin jo aluksi yritin näitä roundeja ja muita, virheitä oli vielä enemmän:-(

Grez [28.04.2019 10:35:44]

#

kyyhky89 kirjoitti:

Olen siis yrittänyt melkein mitä vain, jotta saisin yksinkertaisia lukuja pyöristettyä oikein, mutta se ei vain millään onnistu.

Mitäpäs jos kokeilisit noita asioita mitä sinulle ehdotin jo tuossa 16.04.2019 10:45:15

Grez kirjoitti:

Jos tosiaan liukuluvuissa tulevat virheet kertautuvat, niin voisit käyttää Decimal tai Fractions luokkaa, joista ensimmäisessä voit määritellä tarvittavan tarkkuuden ja jälkimmäinen on tarkkoja arvoja jopa murtoluvuille. Kaikenlaisia muitakin matematiikkakirjastoja varmasti löytyy tarkoilla luvuilla laskemiseen. Kannattaa toki aina muistaa että mitä tarkempaan lasketaan, sitä enemmän suorituskykyä se vaatii.

https://www.programiz.com/python-programming/numbers#dec

Sinänsä tässä päästään sitten lopulta siihen, että jos tietäisi mitä asiaa ohjelmallasi lasketaan, pystyisi ehkä sanomaan miten se oikeastaan kannattaisi tehdä.

kyyhky89 kirjoitti:

Onko nyt kuitenkin niin, että Python ei vain sovellu laskemiseen ja kannattaisi opetella jokin muu kieli? Mitä tulee tuohon edelliseen viestiin, niin jo aluksi yritin näitä roundeja ja muita, virheitä oli vielä enemmän:-(

Ongelma ei ole Pythonissa vaan siinä että haluat väkisin mennä perse edellä puuhun. Sairauksia hoidetaan joskus oireen mukaisesti, mutta se johtuu siitä, että syytä ei tiedetä tai osata hoitaa. Tietotekniikassakin niin joudutaan joskus tekemään (ns. workaround), mutta tässä sinun tapauksessasi syy ongelmaan on ihan täysin tiedossa (eli liukulukujen käyttäminen vaikka haluat tarkkoja arvoja). Jos poistat sen syyn niin oirekin poistuu. Nyt yrität niinsanoakseni hoitaa aivokasvainta buranalla.

Teuro [28.04.2019 10:39:30]

#

Yritä nyt ymmärtää, että se tallennettu luku on liukuluku, joka ei ole tarkka. Grez tuolla jo aiemmin neuvoi joko käyttämään tarkempaan laskentaan kykenevää kirjastoa, tai kertomaan mitä ohjelmasi on tarkoitus tehdä.

kyyhky89 [28.04.2019 11:09:40]

#

Kiitos viestistänne.

Kun olen niin pöhkö, niin voisiko joku yksinkertaistettuna näyttää miten laskutoimistus 41.5*1.75 saadaan kahden desimaalin tarkkuuteen ja samoin 1.1*7. Siis luvun pitä katketa ja käytän tuota lukemaa seuraavissa laskutoimituksissa, jotka ovat aivan yhtä yksinkertaisia.

Grez [28.04.2019 11:46:25]

#

En juurikaan koodaa Pythonilla, mutta googletin (testattu Python 3.5.2:lla). Joku Pythonia aktiivisesti käyttävä kertonee jos tämä hoituu siistimmin tai näppärämmin.

from decimal import *
tulo1 = (Decimal('41.5')*Decimal('1.75')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(tulo1)
tulo2 = Decimal('1.1')*7
print(tulo2)

kyyhky89 [28.04.2019 12:20:33]

#

Voi kiitos. Tämä riittää minulle, kunhan se vain toimii oikein:-)

Grez [28.04.2019 12:33:09]

#

kyyhky89 kirjoitti:

kunhan se vain toimii oikein

Lähtökohtaisesti ohjelmointikielten kirjastot toimivat oikein. Toki joskus löytyy virheitä ja niitä korjataan, mutta ei ole mitään syytä epäillä että Decimal kirjasto ei toimisi oikein. Toisaalta myöskin nuo käyttämäsi liukuluvut toimivat täysin oikein.

Uskoisin kuitenkin, että tuo esimerkkini toimii paitsi oikein, niin myös kuten haluaisit.

Toki decimal-kirjastossa on tiettyjä seikkoja jotka on hyvä tiedostaa, vaikka luulen että ne eivät sinulla muodostu ongelmaksi.

Decimal-kirjaston oletusasetukset ovat:

Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])

(Tuon saa koodilla:

from decimal import *
print(getcontext())

)

Eli asioita joita kannattaa huomioida on, että tarkkuus on _oletuksena_ korkeintaan 28 numeroa (10-järjestelmässä). Lisäksi oletuksena näyttää olevan "pankkiirin pyöristys" eli puolikas pyöristetään kohti parillista desimaalia, kun ilmeisesti haluat tuon esimerkissänikin käyttämän matemaattisen pyöristyksen, jossa puolikas pyöristetään aina ylöspäin. Tuon haluamasi pyöristyssäännön voit valita aina pyöristyksen yhteydessä tai määritellä oletukseksi.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta