Reaali tÔelised omadused, vÔi olge ettevaatlikud REAL-i suhtes

PĂ€rast avaldamist artikleid PostgreSQL-i tĂŒĂŒbid on keerulised, ja esimene kommentaar kĂ€sitles pĂ€risnumbritega töötamise raskusi. Otsustasin kiiresti ĂŒle vaadata koodi, mis mulle SQL-pĂ€ringutena kĂ€tte on antud, et nĂ€ha, kui sageli seal kasutatakse tĂŒĂŒpi REAL. Selgus, et seda kasutatakse ĂŒsna tihti, ja mitte alati ei mĂ”ista arendajad sellega seotud ohte. Ja see mald isegi vaatamata sellele, et internetis ja Habr's on piisavalt hĂ€id artikleid, mis kĂ€sitlevad reaalarvude salvestamise eripĂ€rasid masina mĂ€lus ja nende kasutamist. SeetĂ”ttu pĂŒĂŒan selles artiklis rakendada neid eripĂ€rasid PostgreSQL-ile ning ĂŒritan kĂ€tega seletada seotud probleeme, et SQL-pĂ€ringute arendajad saaksid neist kergemini mööda minna.

PostgreSQL dokumentatsioon sisaldab lĂŒhikest lauset: "Selliste vigade kĂ€itlemine ja nende levik arvutuste kĂ€igus on terve matemaatika ja arvutiteaduse valdkonna Ă”ppeaine, ja seda siin ei kĂ€sitleta" (samuti suunab see ettevaatlikult lugeja IEEE 754 standardi poole). Milliseid vigu siin silmas peetakse? RÀÀgime neist jĂ€rkjĂ€rgult ning varsti selgub, miks ma jĂ€lle kirjutamise kallale asusin.

VÔtame nÀiteks lihtsa pÀringu:

********* PÄRING *********
SELECT 0.1::REAL;
**************************
float4
--------
    0.1
(1 rida)

Tulemuseks ei nĂ€e me midagi erilist – saame oodatud 0.1. Kuid nĂŒĂŒd vĂ”rdleme seda 0.1-ga:

********* PÄRING *********
SELECT 0.1::REAL = 0.1;
**************************
?column?
----------
f
(1 rida)

Ei ole vĂ”rdsed! Millised imed! Kuid see pole veel kĂ”ik. Keegi ĂŒtleb, et ma tean, et REAL halvas tehingudes kĂ€itub halvasti, aga ma hakkan sinna sisestama terveid numbreid, nendega on kindlasti kĂ”ik hĂ€sti. Okei, toome arvu 123 456 789 tĂŒĂŒpi REAL:

********* PÄRING *********
SELECT 123456789::REAL::INT;
**************************
   int4   
-----------
123456792
(1 rida)

Ja see tuli 3 vÔrra suurem! KÔik, andmebaas on tÀiesti lugema unustanud! VÔi ei saa me millestki aru? LÀheme edasi.

Kuna alguseks meenutame teooriat. Nagu on teada, vĂ”ib iga kĂŒmneline arv lagundada kĂŒmniste astmete summaks. Seega number 123.456 on vĂ”rdne 1*10ÂČ + 2*10Âč + 3*10⁰ + 4*10⁻Âč + 5*10⁻ÂČ + 6*10⁻³. Kuid arvuti töötab kahendvormis, seega tuleb need esitada kahendkalduvuste summana. SeetĂ”ttu esindatakse number 5.625 kahendvormis kui 101.101 ja on vĂ”rdne 1*2ÂČ + 0*2Âč + 1*2⁰ + 1*2⁻Âč + 0*2⁻ÂČ + 1*2⁻³. Ja kui positiivsed kahetiseste astmed annavad alati tĂ€isarve (1, 2, 4, 8, 16 jne), siis negatiivsete osas on see keerulisem (0.5, 0.25, 0.125, 0.0625 jne). Probleem seisneb selles, et kĂ”iki kĂŒmnende murde ei saa esitada lĂ”plike kahendmurdena. NĂ€iteks meie kuulus 0.1 esindatakse kahendmurdena kui perioodiline vÀÀrtus 0.0(0011). Seega numbrite lĂ”plik vÀÀrtus masinamuudatustes muutub sĂ”ltuvalt bittide arvust.

NĂŒĂŒd on aeg meenutada, kuidas reaalarve salvestatakse arvuti mĂ€lus. Üldiselt koosneb reaalarv kolmest pĂ”hiosast - mĂ€rgist, mantissist ja eksponentsist. MĂ€rgiks vĂ”ib olla kas pluss vĂ”i miinus, seega kulutab sellele ĂŒks bitt. Kuid mantissi ja eksponendi bittide arv mÀÀrab reaali tĂŒĂŒp. NĂ€iteks REAL tĂŒĂŒbi puhul on mantissi pikkus 23 bitti (ĂŒks bitt, mis on vĂ”rdne 1, lisatakse salaja mantissi ette, ja seetĂ”ttu on kokku 24), ning eksponents on 8 bitti. Kokku on see 32 bitti ehk 4 baiti. TĂŒĂŒp DOUBLE PRECISION puhul on mantissi pikkus juba 52 bitti ja eksponents 11 bitti, kokku 64 bitti ehk 8 baiti. PostgreSQL ei toeta suuremat tĂ€psust ujukomaarvude jaoks.

Pakkime nĂŒĂŒd meie kĂŒmnendilise arvu 0.1 mĂ”lemasse tĂŒĂŒpi - REAL ja DOUBLE PRECISION. Kuna mĂ€rk ja eksponendi vÀÀrtus on meil samad, keskendume mantissile (ma vĂ€ldin teadlikult eksponendi salvestamise ja nullsete reaalarvude mitteilmsed omadused, kuna need raskendavad mĂ”istmist ja segavad probleemi tuumast, kui huvitav - vaadake standardit IEEE 754). Mida me saame? Ülemises reas toon vĂ€lja "mantissi" REAL tĂŒĂŒbi jaoks (arvestades viimane bitt 1 ĂŒmardatakse lĂ€himaks esitatavaks arvuks, vastasel juhul saame 0.099999...), ja alumises REAL tĂŒĂŒbi jaoks DOUBLE PRECISION:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

On selge, et need on kaks tĂ€iesti erinevat numbrit! SeetĂ”ttu, kui me esimese numbri vĂ”rreldes tĂ€iendame seda nullidega, on see seega suurem kui teine (arvesse vĂ”ttes ĂŒmardamist – rĂ”hutatud rasvases kirjas ĂŒhte). Just see seletab meie nĂ€idete mitmeti mĂ”istetavust. Teises nĂ€ites konverteeritakse selgelt mÀÀratud arv 0.1 tĂŒĂŒpi DOUBLE PRECISION, pĂ€rast mida vĂ”rreldakse seda tĂŒĂŒpi REAL numbriga. MĂ”lemad konverteeritakse ĂŒhte tĂŒĂŒpi ja saame tĂ€pselt seda, mida nĂ€eme ĂŒlalpool. Muudame pĂ€ringut, et kĂ”ik omaks oma kohta:

********* PÄRING *********
SELECT 0.1::REAL > 0.1::DOUBLE PRECISION;
**************************
?column?
----------
t
(1 rida)

Ja tÔepoolest, viies 0.1 kahekordselt REAL-iks ja DOUBLE PRECISION-iks, saame vastuse mÔistatusele:

********* PÄRING *********
SELECT 0.1::REAL::DOUBLE PRECISION;
**************************

      float8       
-------------------
0.100000001490116
(1 rida)

Sama seletab ka kolmandat nĂ€idet eespool. Arv 123 456 789 on lihtsalt vĂ”imatu mahutada 24 bitti mantissasse (23 nĂ€htavat + 1 mĂ”istetud). Suurim tĂ€isarv, mis 24 bitis mahub, on 224-1 = 16 777 215. SeetĂ”ttu ĂŒmardatakse meie arv 123 456 789 lĂ€himale esindatavale 123 456 792-le. Muutes tĂŒĂŒbi DOUBLE PRECISION-iks, ei nĂ€e me enam sellist stsenaariumi:

********* PÄRING *********
SELECT 123456789::DOUBLE PRECISION::INT;
**************************
   int4   
-----------
123456789
(1 rida)

Ja ongi kĂ”ik. Selgub, et mingeid imesid pole. Kuid kĂ”ik see, mis on kirjas, on hea pĂ”hjus mĂ”elda, kui tĂ”eliselt on teil vaja tĂŒĂŒpi REAL. TĂ”enĂ€oliselt on tema kasutamise suurim eelis arvutuste kiire teostamine vaatamata juba olemasolevale tĂ€psuse kadumisele. Kuid kas on see universaalne stsenaarium, mis Ă”igustab selle tĂŒĂŒbi nii sagedast kasutamist? Ei, ma ei usu.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster