Pärast avaldamist
PostgreSQL-i dokumentatsioon ütleb lühidalt: "Selliste vigade haldamine ja nende levik arvutamisel on terve matemaatika ja arvutiteaduse haru teema ning seda siin ei käsitleta" (viidates samas lugejat targalt IEEE 754 standardile). Milliseid vigu siin silmas peetakse? Arutame neid järjekorras ja peagi selgub, miks ma uuesti pastaka kätte võtsin.
Võtame näiteks lihtsa taotluse:
********* ЗАПРОС *********
SELECT 0.1::REAL;
**************************
float4
--------
0.1
(1 строка)
Selle tulemusena me midagi erilist ei näe – saame oodatud 0.1. Aga nüüd võrdleme seda 0.1-ga:
********* ЗАПРОС *********
SELECT 0.1::REAL = 0.1;
**************************
?column?
----------
f
(1 строка)
Pole võrdne! Millised imed! Aga edasi, rohkem. Keegi ütleb: ma tean, et REAL käitub murdudega halvasti, nii et sisestan sinna täisarvud ja nendega on kõik kindlasti korras. Ok, anname numbri 123 456 789 REALile:
********* ЗАПРОС *********
SELECT 123456789::REAL::INT;
**************************
int4
-----------
123456792
(1 строка)
Ja tuli veel 3! See on kõik, andmebaas on lõpuks unustanud, kuidas lugeda! Või saame millestki valesti aru? Selgitame välja.
Kõigepealt meenutagem materjali. Nagu teate, saab iga kümnendarvu laiendada kümnendarvuks. Seega on arv 123.456 võrdne 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + 6*10-3. Kuid arvuti töötab numbritega binaarses vormis, seetõttu tuleb need esitada kahe astme laiendusena. Seetõttu on kahendarvuna arv 5.625 esitatud kui 101.101 ja see võrdub 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. Ja kui kahe positiivsed astmed annavad alati täisarvud koma (1, 2, 4, 8, 16 jne), siis negatiivsetega on kõik keerulisem (0.5, 0.25, 0.125, 0,0625 jne). Probleem on selles Iga kümnendkohta ei saa esitada lõpliku kahendmurruna. Seega ilmub meie kurikuulus 0.1 kahendmurru kujul perioodilise väärtusena 0.0(0011). Järelikult varieerub selle arvu lõplik väärtus arvutimälus sõltuvalt biti sügavusest.
Nüüd on aeg meeles pidada, kuidas reaalarvud arvuti mällu salvestatakse. Üldiselt koosneb reaalarv kolmest põhiosast – märgist, mantissist ja astendajast. Märk võib olla kas pluss või miinus, seega eraldatakse selle jaoks üks bitt. Kuid mantissi ja eksponendi bittide arvu määrab tegelik tüüp. Niisiis, REAL tüübi puhul on mantissi pikkus 23 bitti (mantissi algusesse lisatakse kaudselt üks bitt, mis võrdub 1, ja tulemus on 24) ja eksponent on 8 bitti. Kokku on 32 bitti ehk 4 baiti. Ja DOUBLE PRECISION tüübi puhul on mantissi pikkus 52 bitti ja eksponent 11 bitti, kokku 64 bitti ehk 8 baiti. PostgreSQL ei toeta ujukomaarvude suuremat täpsust.
Pakkime oma kümnendarvu 0.1 nii REAL kui ka DOUBLE PRECISION tüüpidesse. Kuna eksponendi märk ja väärtus on samad, keskendume mantissale (jätan teadlikult välja eksponendi väärtuste ja nulli tegelike väärtuste salvestamise mitteilmsemad tunnused, kuna need raskendavad mõistmist ja juhivad tähelepanu olemusest kõrvale Kui olete huvitatud, vaadake IEEE 754 standardit). Mida me saame? Ülemisel real annan PÄRIS tüübi “mantissa” (võttes arvesse viimase biti ümardamist 1 võrra lähima esindatava arvuni, muidu on see 0.099999...) ja alumisel real - jaoks. DOUBLE PRECISION tüüp:
0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001
Ilmselgelt on need kaks täiesti erinevat numbrit! Seetõttu on võrdlemisel esimene number polsterdatud nullidega ja seepärast on see suurem kui teine (arvestades ümardamist - see, mis on märgitud paksus kirjas). See selgitab meie näidete ebaselgust. Teises näites kantakse selgesõnaliselt määratud arv 0.1 tüüpi DOUBLE PRECISION ja võrreldakse seejärel arvuga REAL tüüpi. Mõlemad on taandatud samale tüübile ja meil on täpselt see, mida näeme ülal. Muudame päringut nii, et kõik paika loksuks:
********* ЗАПРОС *********
SELECT 0.1::REAL > 0.1::DOUBLE PRECISION;
**************************
?column?
----------
t
(1 строка)
Ja tõepoolest, tehes arvu 0.1 kahekordse taandamise REAALSEKS ja TOPELTTÄPSUSEKS, saame vastuse mõistatusele:
********* ЗАПРОС *********
SELECT 0.1::REAL::DOUBLE PRECISION;
**************************
float8
-------------------
0.100000001490116
(1 строка)
See selgitab ka ülaltoodud kolmandat näidet. Number 123 456 789 on lihtne mantissi on võimatu 24 bitti mahutada (23 selgesõnalist + 1 kaudset). Maksimaalne täisarv, mis mahub 24 bitti, on 224-1 = 16 777 215. Seetõttu ümardatakse meie arv 123 456 789 lähima esindatava 123 456 792-ni. Kui muudate tüübiks TOOPEPRETSIOON, ei näe me enam seda stsenaariumi:
********* ЗАПРОС *********
SELECT 123456789::DOUBLE PRECISION::INT;
**************************
int4
-----------
123456789
(1 строка)
See on kõik. Selgub, et imesid pole olemas. Kuid kõik kirjeldatu annab hea põhjuse mõelda, kui palju te tegelikult vajate PÄRIS tüüpi. Võib-olla on selle kasutamise suurim eelis teadaoleva täpsuse vähenemisega arvutuste kiirus. Kuid kas see oleks universaalne stsenaarium, mis õigustaks seda tüüpi sagedast kasutamist? Ära mõtle.
Allikas: www.habr.com