Nereālas īstu veidu iezīmes vai esiet uzmanīgi ar REAL

Pēc publicÄ“Å”anas Raksts par rakstÄ«Å”anas iespējām PostgreSQL, pats pirmais komentārs bija par grÅ«tÄ«bām darbā ar reāliem skaitļiem. Es nolēmu ātri apskatÄ«t man pieejamo SQL vaicājumu kodu, lai redzētu, cik bieži tie izmanto REAL veidu. Izrādās, ka tas tiek izmantots diezgan bieži, un izstrādātāji ne vienmēr saprot, kas aiz tā slēpjas. Un tas neskatoties uz to, ka internetā un vietnē HabrĆ© ir diezgan daudz labu rakstu par reālu skaitļu saglabāŔanas iespējām datora atmiņā un par darbu ar tiem. Tāpēc Å”ajā rakstā mēģināŔu pielietot Ŕādas iespējas PostgreSQL un mēģināŔu ātri apskatÄ«t ar tām saistÄ«tās nepatikÅ”anas, lai SQL vaicājumu izstrādātājiem bÅ«tu vieglāk no tām izvairÄ«ties.

PostgreSQL dokumentācijā ir Ä«si teikts: ā€œÅ Ädu kļūdu pārvaldÄ«ba un to izplatÄ«Å”anās aprēķinu laikā ir veselas matemātikas un datorzinātņu nozares priekÅ”mets, un tas Å”eit nav aplÅ«kotsā€ (lai gan lasÄ«tājs gudri atsaucas uz IEEE 754 standartu). Kādas kļūdas Å”eit ir domātas? ApspriedÄ«sim tos secÄ«bā, un drÄ«z kļūs skaidrs, kāpēc es atkal ņēmu rokās pildspalvu.

Ņemsim, piemēram, vienkārÅ”u pieprasÄ«jumu:

********* Š—ŠŠŸŠ ŠžŠ” *********
SELECT 0.1::REAL;
**************************
float4
--------
    0.1
(1 стрŠ¾ŠŗŠ°)

Rezultātā neko Ä«paÅ”u neredzēsim ā€“ iegÅ«sim gaidÄ«to 0.1. Bet tagad salÄ«dzināsim to ar 0.1:

********* Š—ŠŠŸŠ ŠžŠ” *********
SELECT 0.1::REAL = 0.1;
**************************
?column?
----------
f
(1 стрŠ¾ŠŗŠ°)

Nav vienāds! Kādi brÄ«numi! Bet tālāk, vairāk. Kāds teiks: es zinu, ka REAL slikti uzvedas ar daļskaitļiem, tāpēc es ievadÄ«Å”u tur veselus skaitļus, un ar tiem viss noteikti bÅ«s kārtÄ«bā. Labi, ievadÄ«sim skaitli 123 456 789 uz REAL:

********* Š—ŠŠŸŠ ŠžŠ” *********
SELECT 123456789::REAL::INT;
**************************
   int4   
-----------
123456792
(1 стрŠ¾ŠŗŠ°)

Un izrādījās, ka ir vēl 3! Tas tā, datubāze beidzot ir aizmirsusi, kā skaitīt! Vai arī mēs kaut ko pārpratām? Izdomāsim.

Pirmkārt, atcerēsimies materiālu. Kā jÅ«s zināt, jebkuru decimālo skaitli var izvērst desmit pakāpēs. Tātad skaitlis 123.456 bÅ«s vienāds ar 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ā€‹ā€‹ā€‹ā€‹6*10-3. Bet dators darbojas ar skaitļiem binārā formā, tāpēc tie ir jāattēlo izvērsuma veidā ar diviem pakāpēm. Tāpēc skaitlis 5.625 binārajā formā tiek attēlots kā 101.101 un bÅ«s vienāds ar 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. Un, ja divu pozitÄ«vās pakāpes vienmēr dod veselus skaitļus aiz komata (1, 2, 4, 8, 16 utt.), tad ar negatÄ«vajiem viss ir sarežģītāk (0.5, 0.25, 0.125, 0,0625 utt.). Problēma ir tāda Ne katru decimāldaļu var attēlot kā ierobežotu bināru daļu. Tādējādi mÅ«su bēdÄ«gi slavenais 0.1 bināras daļas formā parādās kā periodiskā vērtÄ«ba 0.0(0011). LÄ«dz ar to Ŕī skaitļa galÄ«gā vērtÄ«ba datora atmiņā mainÄ«sies atkarÄ«bā no bitu dziļuma.

Tagad ir pienācis laiks atcerēties, kā reālie skaitļi tiek saglabāti datora atmiņā. VispārÄ«gi runājot, reāls skaitlis sastāv no trim galvenajām daļām - zÄ«mes, mantisas un eksponenta. ZÄ«me var bÅ«t vai nu plus, vai mÄ«nus, tāpēc tam tiek atvēlēts viens bits. Bet mantisas un eksponenta bitu skaitu nosaka reālais tips. Tātad REAL tipam mantisas garums ir 23 biti (mantisas sākumam netieÅ”i tiek pievienots viens bits, kas vienāds ar 1, un rezultāts ir 24), un eksponents ir 8 biti. Kopā ir 32 biti jeb 4 baiti. Un DOUBLE PRECISION tipam mantisas garums bÅ«s 52 biti, un eksponents bÅ«s 11 biti, kopā 64 biti jeb 8 baiti. PostgreSQL neatbalsta augstāku peldoŔā komata skaitļu precizitāti.

Iepakosim savu decimālskaitli 0.1 gan REAL, gan DOUBLE PRECISION veidos. Tā kā eksponenta zÄ«me un vērtÄ«ba ir vienāda, mēs koncentrēsimies uz mantisu (es apzināti izlaižu nepārprotamās eksponenta vērtÄ«bu un nulles reālās vērtÄ«bas saglabāŔanas iezÄ«mes, jo tās sarežģī izpratni un novērÅ” uzmanÄ«bu no bÅ«tÄ«bas Ja interesē, skatiet IEEE 754 standartu). Ko mēs iegÅ«sim? AugŔējā rindā doÅ”u ā€œmantisuā€ ÄŖSTAM tipam (ņemot vērā pēdējā bita noapaļoÅ”anu par 1 lÄ«dz tuvākajam attēlojamajam skaitlim, pretējā gadÄ«jumā tas bÅ«s 0.099999...), bet apakŔējā rindā - par. DOUBLE PRECISION tips:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

AcÄ«mredzot tie ir divi pilnÄ«gi atŔķirÄ«gi skaitļi! Tāpēc, salÄ«dzinot, pirmais cipars tiks polsterēts ar nullēm un lÄ«dz ar to bÅ«s lielāks par otro (ņemot vērā noapaļoÅ”anu ā€“ treknrakstā atzÄ«mētais). Tas izskaidro mÅ«su piemēru neskaidrÄ«bu. Otrajā piemērā skaidri norādÄ«tais skaitlis 0.1 tiek nodots DOUBLE PRECISION tipam un pēc tam salÄ«dzināts ar ÄŖSTĀ tipa skaitli. Abi ir samazināti lÄ«dz tādam paÅ”am tipam, un mums ir tieÅ”i tas, ko mēs redzam iepriekÅ”. Modificēsim vaicājumu, lai viss nonāktu savās vietās:

********* Š—ŠŠŸŠ ŠžŠ” *********
SELECT 0.1::REAL > 0.1::DOUBLE PRECISION;
**************************
?column?
----------
t
(1 стрŠ¾ŠŗŠ°)

Un tieŔām, veicot skaitļa 0.1 dubultu samazināŔanu uz REĀLU un DUBULU PRECIZITĀTI, mēs iegÅ«stam atbildi uz mÄ«klu:

********* Š—ŠŠŸŠ ŠžŠ” *********
SELECT 0.1::REAL::DOUBLE PRECISION;
**************************

      float8       
-------------------
0.100000001490116
(1 стрŠ¾ŠŗŠ°)

Tas arÄ« izskaidro treÅ”o piemēru iepriekÅ”. Skaitlis 123 456 789 ir vienkārÅ”s mantisu nav iespējams ievietot 24 bitos (23 tieÅ”i + 1 netieÅ”s). Maksimālais veselais skaitlis, ko var ietilpt 24 bitos, ir 224-1 = 16 777 215. Tāpēc mÅ«su skaitlis 123 456 789 ir noapaļots lÄ«dz tuvākajam reprezentējamajam 123 456 792. Mainot veidu uz DOUBLE PRECISION, mēs vairs neredzam Å”o scenāriju:

********* Š—ŠŠŸŠ ŠžŠ” *********
SELECT 123456789::DOUBLE PRECISION::INT;
**************************
   int4   
-----------
123456789
(1 стрŠ¾ŠŗŠ°)

Tas ir viss. Izrādās, ka brÄ«numu nav. Bet viss aprakstÄ«tais ir labs iemesls padomāt par to, cik ļoti jums patieŔām ir vajadzÄ«gs ÄŖSTAIS tips. Iespējams, ka lielākā tā izmantoÅ”anas priekÅ”rocÄ«ba ir aprēķinu ātrums ar zināmu precizitātes zudumu. Bet vai tas bÅ«tu universāls scenārijs, kas attaisnotu tik biežu Ŕāda veida izmantoÅ”anu? Nedomājiet.

Avots: www.habr.com

Pievieno komentāru