Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++ Liukulukujen tarkkuus

Sivun loppuun

vesikuusi [08.01.2014 22:50:44]

#

Moi. Ihmettelen seuraavanlaista juttua ja toivon, että joku haluaisi selittää asiaa minulle. Miksi alla olevan koodin viimeinen cout-rivi antaa erilaisen tuloksen kuin kaksi ylempää?

#include <iostream>
#include <cmath>

int main () {
    std::cout << ( 0.02 - 0.02 ) << std::endl;
    std::cout << ( std::abs ( -0.02 ) - 0.02 ) << std::endl;
    std::cout << ( std::abs ( 1.98 - 2 ) - 0.02 ) << std::endl;

    return 0;
}
0
0
1.73472e-17

1.73472e-17 on kyllä erittäin pieni luku, mutta minusta on silti kummallista, etä tulos ei ole 0, kun sitä ensisilmäyksellä odottaisi. Mistä lienee kysymys?

Lisäys:

Tein vielä yhden testin:

int main () {
    std::cout << ( std::abs ( 1.98 - 2 ) - ( std::abs ( 1.98 - 2 ) ) << std::endl;

    return 0;
}

Tässä tulos onkin 0. Johtuuko se mahdollisesti samoista/eri tietotyypeistä?

Edit. Ilmeisesti johtuu. Mihin tämä käyttäytyminen tarkalleen perustuu?

Antti Laaksonen [08.01.2014 23:05:50]

#

Ilmiö johtuu siitä, että monia lukuja ei voi esittää tarkasti liukulukuna. Tässä on toinen testiohjelma:

#include <iostream>
#include <iomanip>

using namespace std;

int main() {
    double a = 0.1;
    cout << setprecision(20); // 20 desimaalin tarkkuus
    cout << a << endl;
}

Ohjelma tulostaa 0.10000000000000000555, joka on mahdollisimman lähellä lukua 0.1 oleva double-liukuluku. Vastaavasti luvusta 0.2 tulee 0.2000000000000000111. Mutta luvun 0.5 pystyy esittämään tarkasti liukulukuna.

Tuolla ohjelmalla voit testata mistä tahansa luvusta, voiko sen esittää tarkasti liukulukuna. Sitten jos liukuluvuilla tehdään laskutoimituksia, voi tulla monenlaisia uusia pyöristysvirheitä.

vesikuusi [08.01.2014 23:35:41]

#

Okei, mielenkiintoista! Kiitos vastauksesta.

Grez [09.01.2014 11:55:52]

#

Niin, yleisesti ottaen jos luku on kerrottavissa kokonaisluvuksi jollain kahden potenssilla, sen voi esittää tarkasti. (Toki tarkkuus katoaa myös jos merkitseviä numeroita on paljon.)

Tzaeru [10.01.2014 08:34:07]

#

Ja tämä ei varsinaisesti ole niinkään C++:n ominaisuus, kuin FPU:n ja sen tekniikan, miten tietokone liukulukujen laskun ja talletuksen hoitaa.

Muunmuassa valuuttaa käsittelevissä ohjelmissa pyritään olemaan käyttämättä liukulukuja mahdollisten tarkkuusvirheiden takia.

vesikuusi [10.01.2014 11:48:39]

#

Aivan. Joo, tuosta olenkin tietoinen, että valuuttaa ei kannata liukuluvuilla käsitellä. On tullut itsekin huomattua, että liukuluvuiksi tallennettujen lukujen summat voivat olla vähän yllättäviä (jos asiaa tarkastelee intuitivisesti eikä teknisesti).

Lisäys:

En silti vieläkään ihan ymmärrä, miksi nämä seuraavat laskutoimitukset antavat eri tulokset.

#include <iostream>
#include <cmath>

int main () {
    std::cout << ( std::abs ( -0.02 ) - 0.02 ) << std::endl;
    std::cout << ( std::abs ( 1.98 - 2 ) - 0.02 ) << std::endl;

    return 0;
}
0
1.73472e-17

Eikö molemmissa ole samat tietotyypit samassa järjestyksessä?

The Alchemist [10.01.2014 12:21:18]

#

vesikuusi kirjoitti:

En silti vieläkään ihan ymmärrä, miksi nämä seuraavat laskutoimitukset antavat eri tulokset.

Eikö sinulle ole koko ajan yritetty teroittaa sitä, etteivät liukuluvut ole eksakteja? "1.98" ei ole tasan 1.980000000000000 eikä "2.00 - 1.98" ole tasan 0.020000000000000.

vesikuusi [10.01.2014 12:43:07]

#

Aa joo aivan nyt tajusin. :D Unohdin tosiaan sen seikan, että 1.98 ei tietenkään ole tasan 1.98.

feenix [11.01.2014 10:46:14]

#

Tämän jälkeen ymmärtänee helposti myös sen, miksi ei saa "ikinä" tehdä näin:

double b = 1.98 - 2;
if (b == -0.02) puts("JEE!");

Sivun alkuun

Vastaus

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

Tietoa sivustosta