Karakteristikat joreale të llojeve reale, ose kini kujdes me REAL

Pas publikimit Artikull në lidhje me veçoritë e shtypjes në PostgreSQL, komenti i parë ishte për vështirësitë e punës me numra realë. Vendosa t'i hedh një vështrim të shpejtë kodit të pyetjeve SQL të disponueshme për mua për të parë se sa shpesh përdorin llojin REAL. Rezulton se përdoret mjaft shpesh, dhe zhvilluesit jo gjithmonë i kuptojnë rreziqet pas tij. Dhe kjo pavarësisht nga fakti se ka mjaft artikuj të mirë në internet dhe në Habré për veçoritë e ruajtjes së numrave realë në kujtesën e kompjuterit dhe për punën me ta. Prandaj, në këtë artikull unë do të përpiqem të aplikoj funksione të tilla në PostgreSQL dhe do të përpiqem të hedh një vështrim të shpejtë në problemet që lidhen me to, në mënyrë që zhvilluesit e pyetjeve SQL ta kenë më të lehtë t'i shmangin ato.

Dokumentacioni PostgreSQL thotë në mënyrë të përmbledhur: "Menaxhimi i gabimeve të tilla dhe përhapja e tyre gjatë llogaritjes është objekt i një dege të tërë të matematikës dhe shkencave kompjuterike dhe nuk mbulohet këtu" (ndërsa me mençuri i referohet lexuesit standardit IEEE 754). Çfarë lloj gabimesh nënkuptohen këtu? Le t'i diskutojmë ato me radhë dhe së shpejti do të bëhet e qartë pse e mora përsëri stilolapsin.

Le të marrim për shembull një kërkesë të thjeshtë:

********* ЗАПРОС *********
SELECT 0.1::REAL;
**************************
float4
--------
    0.1
(1 строка)

Si rezultat, ne nuk do të shohim asgjë të veçantë - do të marrim 0.1 të pritur. Por tani le ta krahasojmë me 0.1:

********* ЗАПРОС *********
SELECT 0.1::REAL = 0.1;
**************************
?column?
----------
f
(1 строка)

Jo i barabartë! Çfarë mrekullish! Por më tej, më shumë. Dikush do të thotë, e di që REAL sillet keq me thyesat, kështu që unë do të fus numra të plotë atje dhe gjithçka do të jetë padyshim mirë me ta. Ok, le të hedhim numrin 123 në REAL:

********* ЗАПРОС *********
SELECT 123456789::REAL::INT;
**************************
   int4   
-----------
123456792
(1 строка)

Dhe doli të ishin 3 të tjera! Kjo është ajo, baza e të dhënave më në fund ka harruar si të numërojë! Apo po keqkuptojmë diçka? Le ta kuptojmë.

Së pari, le të kujtojmë materialin. Siç e dini, çdo numër dhjetor mund të zgjerohet në fuqitë e dhjetë. Pra, numri 123.456 do të jetë i barabartë me 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ​​6*10-3. Por kompjuteri funksionon me numra në formë binare, prandaj ata duhet të paraqiten në formën e zgjerimit në fuqitë e dy. Prandaj, numri 5.625 në binar përfaqësohet si 101.101 dhe do të jetë i barabartë me 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. Dhe nëse fuqitë pozitive të dyve japin gjithmonë numra dhjetorë të plotë (1, 2, 4, 8, 16, etj.), Atëherë me ato negative gjithçka është më e ndërlikuar (0.5, 0.25, 0.125, 0,0625, etj.). Problemi është se Jo çdo dhjetore mund të përfaqësohet si një thyesë binare e fundme. Kështu, 0.1 ynë famëkeq në formën e një fraksioni binar shfaqet si vlera periodike 0.0 (0011). Rrjedhimisht, vlera përfundimtare e këtij numri në kujtesën e kompjuterit do të ndryshojë në varësi të thellësisë së bitit.

Tani është koha për të kujtuar se si numrat realë ruhen në memorien e kompjuterit. Në përgjithësi, një numër real përbëhet nga tre pjesë kryesore - shenja, mantisa dhe eksponent. Shenja mund të jetë ose plus ose minus, kështu që një bit është ndarë për të. Por numri i pjesëve të mantisës dhe eksponentit përcaktohet nga lloji real. Pra, për llojin REAL, gjatësia e mantisës është 23 bit (një bit i barabartë me 1 shtohet në mënyrë implicite në fillim të mantisës, dhe rezultati është 24), dhe eksponenti është 8 bit. Gjithsej është 32 bit, ose 4 bajt. Dhe për llojin DOUBLE PRECISION, gjatësia e mantisës do të jetë 52 bit, dhe eksponenti do të jetë 11 bit, për një total prej 64 bit, ose 8 bit. PostgreSQL nuk mbështet saktësi më të lartë për numrat me pikë lundruese.

Le ta paketojmë numrin tonë dhjetor 0.1 në të dy llojet REAL dhe TË DYFISHTE PRECISION. Meqenëse shenja dhe vlera e eksponentit janë të njëjta, ne do të përqendrohemi në mantisa (Unë me dashje heq veçoritë jo të dukshme të ruajtjes së vlerave të eksponentit dhe vlerave zero reale, pasi ato ndërlikojnë kuptimin dhe shpërqendrojnë nga thelbi për problemin, nëse jeni të interesuar, shihni standardin IEEE 754). Çfarë do të marrim? Në rreshtin e sipërm do të jap "mantisa" për llojin REAL (duke marrë parasysh rrumbullakimin e bitit të fundit me 1 në numrin më të afërt të përfaqësuar, përndryshe do të jetë 0.099999...), dhe në fund - për lloji me precizion të dyfishtë:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Natyrisht, këto janë dy numra krejtësisht të ndryshëm! Prandaj, kur krahasojmë, numri i parë do të jetë i mbushur me zero dhe, për rrjedhojë, do të jetë më i madh se i dyti (duke marrë parasysh rrumbullakimin - atë të shënuar me shkronja të zeza). Kjo shpjegon paqartësinë nga shembujt tanë. Në shembullin e dytë, numri i specifikuar në mënyrë eksplicite 0.1 është hedhur në llojin me saktësi të dyfishtë dhe më pas krahasohet me një numër të tipit REAL. Të dyja janë reduktuar në të njëjtin lloj, dhe ne kemi pikërisht atë që shohim më lart. Le të modifikojmë pyetjen në mënyrë që gjithçka të bjerë në vend:

********* ЗАПРОС *********
SELECT 0.1::REAL > 0.1::DOUBLE PRECISION;
**************************
?column?
----------
t
(1 строка)

Dhe në të vërtetë, duke kryer një zvogëlim të dyfishtë të numrit 0.1 në REAL dhe SAKTËSI TË DYFISHTE, marrim përgjigjen e enigmës:

********* ЗАПРОС *********
SELECT 0.1::REAL::DOUBLE PRECISION;
**************************

      float8       
-------------------
0.100000001490116
(1 строка)

Kjo shpjegon edhe shembullin e tretë të mësipërm. Numri 123 është i thjeshtë është e pamundur të përshtatet mantisa në 24 bit (23 eksplicite + 1 nënkuptohet). Numri i plotë maksimal që mund të futet në 24 bit është 224-1 = 16. Prandaj, numri ynë 777 është i rrumbullakosur në 215 të përfaqësueshme më të afërt. Duke ndryshuar llojin në SAKTËSI TË DYFISHTË, ne nuk e shohim më këtë skenar:

********* ЗАПРОС *********
SELECT 123456789::DOUBLE PRECISION::INT;
**************************
   int4   
-----------
123456789
(1 строка)

Kjo eshte e gjitha. Rezulton se nuk ka mrekulli. Por gjithçka e përshkruar është një arsye e mirë për të menduar se sa shumë keni nevojë për llojin REAL. Ndoshta avantazhi më i madh i përdorimit të tij është shpejtësia e llogaritjeve me një humbje të njohur të saktësisë. Por a do të ishte ky një skenar universal që do të justifikonte përdorimin kaq të shpeshtë të këtij lloji? Mos mendo.

Burimi: www.habr.com

Shto një koment