Valódi típusok irreális jellemzői, vagy légy óvatos a REAL-lal

Megjelenés után Cikk A PostgreSQL-ben való gépelés jellemzőiről a legelső megjegyzés a valós számokkal való munka nehézségeiről szólt. Úgy döntöttem, hogy gyorsan megnézem a rendelkezésemre álló SQL-lekérdezések kódját, hogy megtudjam, milyen gyakran használják a REAL típust. Kiderült, hogy elég gyakran használják, és a fejlesztők nem mindig értik a mögötte rejlő veszélyeket. És mindez annak ellenére, hogy az interneten és a Habré-n nagyon sok jó cikk található a valós számok számítógép memóriájában való tárolásának jellemzőiről és a velük való munkavégzésről. Ezért ebben a cikkben megpróbálom alkalmazni ezeket a funkciókat a PostgreSQL-re, és megpróbálom gyorsan áttekinteni a velük kapcsolatos problémákat, hogy az SQL lekérdezések fejlesztői könnyebben elkerülhessék őket.

A PostgreSQL dokumentációja tömören leszögezi: „Az ilyen hibák kezelése és terjedésük a számítás során a matematika és számítástechnika egy egész ágának tárgya, és itt nem foglalkozunk vele” (miközben bölcsen az IEEE 754 szabványra utalva az olvasót). Milyen hibákat értünk itt? Beszéljük meg őket sorban, és hamarosan kiderül, miért fogtam újra a tollat.

Vegyünk például egy egyszerű kérést:

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

Ennek eredményeként nem fogunk látni semmi különöset – megkapjuk a várt 0.1-et. De most hasonlítsuk össze a 0.1-el:

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

Nem egyenlő! Micsoda csodák! De tovább, többet. Valaki azt fogja mondani, tudom, hogy a REAL rosszul viselkedik a törtekkel, ezért egész számokat írok be, és minden rendben lesz velük. Rendben, adjuk át a 123 456 789-es számot a REAL-nak:

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

És még 3 lett belőle! Ez az, az adatbázis végre elfelejtette, hogyan kell számolni! Vagy valamit félreértünk? Találjuk ki.

Először is emlékezzünk az anyagra. Mint tudják, bármely tizedes szám tízes hatványokra bővíthető. Tehát a 123.456 szám egyenlő lesz: 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ​​6*10-3. De a számítógép bináris formában működik a számokkal, ezért azokat kettős hatványokban kell ábrázolni. Ezért a bináris 5.625-ös szám 101.101-ként jelenik meg, és egyenlő lesz 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. És ha kettő pozitív hatványai mindig egész tizedes számokat adnak (1, 2, 4, 8, 16 stb.), akkor a negatívakkal minden bonyolultabb (0.5, 0.25, 0.125, 0,0625 stb.). Az a probléma Nem minden tizedes ábrázolható véges bináris törtként. Így a hírhedt 0.1-ünk bináris tört alakjában 0.0(0011) periodikus értékként jelenik meg. Következésképpen ennek a számnak a végső értéke a számítógép memóriájában a bitmélységtől függően változik.

Itt az ideje, hogy emlékezzünk arra, hogyan tárolódnak a valós számok a számítógép memóriájában. Általánosságban elmondható, hogy a valós szám három fő részből áll - előjelből, mantisszából és kitevőből. Az előjel lehet plusz vagy mínusz, tehát egy bit van hozzárendelve. De a mantissza és a kitevő bitjeinek számát a valós típus határozza meg. Tehát a REAL típusnál a mantissza hossza 23 bit (egy 1-gyel egyenlő bit implicit módon hozzáadódik a mantissza elejéhez, és az eredmény 24), a kitevő pedig 8 bit. Az összesen 32 bit vagy 4 bájt. A DOUBLE PRECISION típusnál pedig a mantissza hossza 52 bit, a kitevője pedig 11 bit, azaz összesen 64 bit, vagyis 8 bájt. A PostgreSQL nem támogatja a lebegőpontos számok nagyobb pontosságát.

Tegyük bele a 0.1-es decimális számunkat REAL és DOUBLE PRECISION típusokba is. Mivel a kitevő előjele és értéke megegyezik, a mantisszára fogunk összpontosítani (szándékosan kihagyom a kitevő értékeinek és a nulla valós értékek tárolásának nem nyilvánvaló jellemzőit, mivel megnehezítik a megértést és elvonják a figyelmet a lényegről Ha érdekel, lásd az IEEE 754 szabványt). Mit fogunk kapni? A felső sorban megadom a „mantisszát” a REAL típushoz (figyelembe véve az utolsó bit 1-gyel történő kerekítését a legközelebbi ábrázolható számra, különben 0.099999 lesz...), az alsó sorban pedig - a DOUBLE PRECISION típus:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Nyilvánvalóan ez két teljesen különböző szám! Ezért összehasonlításkor az első szám nullákkal lesz kitöltve, és ezért nagyobb lesz, mint a második (a kerekítést figyelembe véve - a félkövérrel jelölt szám). Ez magyarázza a példáink kétértelműségét. A második példában az explicit módon megadott 0.1-es szám a DOUBLE PRECISION típusba kerül, majd összehasonlításra kerül a REAL típusú számmal. Mindkettő ugyanarra a típusra redukálódik, és pontosan az van, amit fent látunk. Módosítsuk a lekérdezést, hogy minden a helyére kerüljön:

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

És valóban, a 0.1 szám kétszeres VALÓDI és DUPLÁS PONTOSSÁGRA redukálásával megkapjuk a választ a rejtvényre:

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

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

Ez magyarázza a fenti harmadik példát is. A 123 456 789 szám egyszerű lehetetlen a mantisszát 24 bitbe illeszteni (23 explicit + 1 hallgatólagos). A 24 bitbe beférő maximális egész szám: 224-1 = 16 777 215. Ezért a 123 456 789-es számunkat a legközelebbi reprezentatív 123 456 792-re kerekítjük. Ha a típust DOUBLE PRECISION-ra változtatjuk, többé nem látjuk ezt a forgatókönyvet:

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

Ez minden. Kiderült, hogy csodák nincsenek. De minden leírt jó ok arra, hogy elgondolkozz azon, mennyire van szükséged az IGAZI típusra. Használatának talán legnagyobb előnye a számítások gyorsasága, ismert pontosságvesztéssel. De vajon ez egy univerzális forgatókönyv lenne, amely indokolná ennek a típusnak a gyakori használatát? Ne gondolkozz.

Forrás: will.com

Hozzászólás