Onwerklike kenmerke van regte tipes, of wees versigtig met REAL

Na publikasie Artikel oor die kenmerke van tik in PostgreSQL, was die heel eerste opmerking oor die probleme om met reële getalle te werk. Ek het besluit om vinnig te kyk na die kode van die SQL-navrae wat vir my beskikbaar is om te sien hoe gereeld hulle die REAL-tipe gebruik. Dit blyk dat dit redelik gereeld gebruik word, en ontwikkelaars verstaan ​​nie altyd die gevare daaragter nie. En dit ondanks die feit dat daar heelwat goeie artikels op die internet en op Habré is oor die kenmerke van die stoor van reële getalle in rekenaargeheue en oor die werk daarmee. Daarom sal ek in hierdie artikel probeer om sulke kenmerke op PostgreSQL toe te pas, en sal ek probeer om vinnig te kyk na die probleme wat daarmee gepaard gaan, sodat dit makliker sal wees vir SQL-navraagontwikkelaars om dit te vermy.

Die PostgreSQL-dokumentasie sê bondig: "Die bestuur van sulke foute en hul verspreiding tydens berekening is die onderwerp van 'n hele tak van wiskunde en rekenaarwetenskap, en word nie hier gedek nie" (terwyl die leser wyslik na die IEEE 754-standaard verwys word). Watter soort foute word hier bedoel? Kom ons bespreek hulle in volgorde, en dit sal gou duidelik word hoekom ek weer die pen opgeneem het.

Kom ons neem byvoorbeeld 'n eenvoudige versoek:

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

As gevolg hiervan sal ons niks spesiaals sien nie - ons sal die verwagte 0.1 kry. Maar kom ons vergelyk dit nou met 0.1:

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

Nie gelyk nie! Watter wonderwerke! Maar verder, meer. Iemand sal sê, ek weet dat REAL sleg optree met breuke, so ek sal heelgetalle daar invoer, en alles sal beslis reg wees met hulle. Goed, kom ons gooi die nommer 123 na REAL:

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

En dit blyk nog 3 te wees! Dit is dit, die databasis het uiteindelik vergeet hoe om te tel! Of verstaan ​​ons iets verkeerd? Kom ons vind dit uit.

Kom ons onthou eers die materiaal. Soos u weet, kan enige desimale getal uitgebrei word in magte van tien. Dus, die getal 123.456 sal gelyk wees aan 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ​​​​6*10-3. Maar die rekenaar werk met getalle in binêre vorm, daarom moet hulle voorgestel word in die vorm van uitbreiding in magte van twee. Daarom word die getal 5.625 in binêre voorgestel as 101.101 en sal gelyk wees aan 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. En as positiewe magte van twee altyd heel desimale getalle gee (1, 2, 4, 8, 16, ens.), dan is alles met negatiewes meer ingewikkeld (0.5, 0.25, 0.125, 0,0625, ens.). Die probleem is dit Nie elke desimale kan as 'n eindige binêre breuk voorgestel word nie. Dus, ons berugte 0.1 in die vorm van 'n binêre breuk verskyn as die periodieke waarde 0.0(0011). Gevolglik sal die finale waarde van hierdie getal in rekenaargeheue wissel na gelang van die bisdiepte.

Nou is die tyd om te onthou hoe reële getalle in rekenaargeheue gestoor word. Oor die algemeen bestaan ​​'n reële getal uit drie hoofdele - teken, mantisse en eksponent. Die teken kan óf plus óf minus wees, so een bietjie word daarvoor toegeken. Maar die aantal bisse van die mantisse en eksponent word bepaal deur die werklike tipe. Dus, vir die REAL tipe, is die lengte van die mantisse 23 bisse (een bis gelyk aan 1 word implisiet by die begin van die mantisse gevoeg, en die resultaat is 24), en die eksponent is 8 bisse. Die totaal is 32 bisse, of 4 grepe. En vir die DUBBELE PRESISIE tipe sal die lengte van die mantisse 52 bisse wees, en die eksponent sal 11 bisse wees, vir 'n totaal van 64 bisse, of 8 grepe. PostgreSQL ondersteun nie hoër presisie vir swaaipuntgetalle nie.

Kom ons pak ons ​​desimale getal 0.1 in beide WERKLIKE en DUBBELE PRESISIE tipes. Aangesien die teken en waarde van die eksponent dieselfde is, sal ons op die mantisse fokus (ek laat doelbewus die nie-vanselfsprekende kenmerke van die stoor van die waardes van die eksponent en nul reële waardes weg, aangesien dit begrip bemoeilik en aandag aflei van die essensie van die probleem, indien belangstel, sien die IEEE 754-standaard). Wat sal ons kry? In die boonste reël sal ek die “mantissa” vir die REAL tipe gee (met inagneming van die afronding van die laaste bietjie met 1 tot die naaste verteenwoordigbare getal, anders sal dit 0.099999 wees...), en in die onderste reël - vir die DUBBELE PRESISIE tipe:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Dit is duidelik twee heeltemal verskillende getalle! Daarom, wanneer dit vergelyk word, sal die eerste getal met nulle opgevul word en sal dus groter wees as die tweede (met inagneming van afronding - die een wat vetgedruk is). Dit verklaar die onduidelikheid uit ons voorbeelde. In die tweede voorbeeld word die eksplisiet gespesifiseerde nommer 0.1 na die DUBBELE PRESISIE tipe gegiet, en dan vergelyk met 'n getal van die REAL tipe. Albei is tot dieselfde tipe gereduseer, en ons het presies wat ons hierbo sien. Kom ons verander die navraag sodat alles in plek val:

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

En inderdaad, deur 'n dubbele vermindering van die getal 0.1 uit te voer tot WERKLIKE en DUBBEL PRESISIE, kry ons die antwoord op die raaisel:

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

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

Dit verduidelik ook die derde voorbeeld hierbo. Die nommer 123 456 789 is eenvoudig dit is onmoontlik om die mantissa in 24 bisse te pas (23 eksplisiet + 1 geïmpliseer). Die maksimum heelgetal wat in 24 bisse kan pas is 224-1 = 16 777 215. Daarom word ons getal 123 456 789 afgerond tot die naaste verteenwoordigbare 123 456 792. Deur die tipe na DUBBELE PRESISIE te verander, sien ons nie meer hierdie scenario nie:

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

Dis al. Dit blyk dat daar geen wonderwerke is nie. Maar alles wat beskryf word, is 'n goeie rede om na te dink oor hoeveel jy die REGTE tipe regtig nodig het. Miskien is die grootste voordeel van die gebruik daarvan die spoed van berekeninge met 'n bekende verlies aan akkuraatheid. Maar sou dit 'n universele scenario wees wat so gereelde gebruik van hierdie tipe sou regverdig? Moenie dink nie.

Bron: will.com

Voeg 'n opmerking