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?
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ä.
Okei, mielenkiintoista! Kiitos vastauksesta.
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.)
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.
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ä?
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.
Aa joo aivan nyt tajusin. :D Unohdin tosiaan sen seikan, että 1.98 ei tietenkään ole tasan 1.98.
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!");
Aihe on jo aika vanha, joten et voi enää vastata siihen.