Óraunverulegir eiginleikar raunverulegra tegunda, eða farðu varlega með REAL

Eftir birtingu Grein um eiginleika þess að slá inn PostgreSQL, fyrsta athugasemdin var um erfiðleikana við að vinna með rauntölur. Ég ákvað að kíkja fljótt á kóðann fyrir SQL fyrirspurnirnar sem eru tiltækar fyrir mig til að sjá hversu oft þær nota REAL tegundina. Það kemur í ljós að það er notað nokkuð oft og forritarar skilja ekki alltaf hættuna á bak við það. Og það þrátt fyrir að það sé töluvert mikið af góðum greinum á netinu og á Habré um eiginleika þess að geyma rauntölur í tölvuminni og um að vinna með þær. Þess vegna mun ég í þessari grein reyna að beita slíkum eiginleikum á PostgreSQL og mun reyna að líta fljótt á vandræðin sem tengjast þeim, svo að það verði auðveldara fyrir SQL fyrirspurnarforritara að forðast þau.

Í PostgreSQL skjölunum segir í stuttu máli: „Meðhöndlun slíkra villna og útbreiðslu þeirra við útreikning er viðfangsefni heillar greinar stærðfræði og tölvunarfræði og er ekki fjallað um það hér“ (en vísar lesandanum skynsamlega í IEEE 754 staðlinum). Hvers konar villur er átt við hér? Við skulum ræða þau í röð og fljótlega kemur í ljós hvers vegna ég tók upp pennann aftur.

Tökum sem dæmi einfalda beiðni:

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

Fyrir vikið munum við ekki sjá neitt sérstakt - við fáum væntanlega 0.1. En nú skulum við bera það saman við 0.1:

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

Ekki jafnt! Þvílík kraftaverk! En lengra, meira. Einhver mun segja, ég veit að REAL hegðar sér illa með brot, svo ég mun slá inn heilar tölur þar, og allt verður örugglega í lagi með þær. Allt í lagi, við skulum varpa númerinu 123 í REAL:

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

Og það reyndust vera 3 í viðbót! Það er það, gagnagrunnurinn hefur loksins gleymt hvernig á að telja! Eða erum við að misskilja eitthvað? Við skulum reikna það út.

Fyrst skulum við muna eftir efninu. Eins og þú veist er hægt að stækka hvaða aukastaf sem er í tíu veldi. Þannig að talan 123.456 verður jöfn 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ​​​​6*10-3. En tölvan starfar með tölum í tvíundarformi, þess vegna verða þær að vera táknaðar í formi stækkunar í tveimur valdi. Þess vegna er talan 5.625 í tvöfaldri táknuð sem 101.101 og verður jöfn 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. Og ef jákvæðir kraftar tveggja gefa alltaf heilar aukastafir (1, 2, 4, 8, 16, osfrv.), þá er allt flóknara með neikvæðum (0.5, 0.25, 0.125, 0,0625, osfrv.). Vandamálið er það Ekki er hægt að tákna alla tuga sem endanlega tvöfalda brot. Þannig birtist hið alræmda 0.1 okkar í formi tvíundarbrots sem lotugildið 0.0(0011). Þar af leiðandi mun lokagildi þessarar tölu í minni tölvunnar vera mismunandi eftir bitadýptinni.

Nú er kominn tími til að muna hvernig rauntölur eru geymdar í tölvuminni. Almennt séð samanstendur rauntala af þremur meginhlutum - tákni, mantissa og veldisvísi. Merkið getur verið annaðhvort plús eða mínus, þannig að einum bita er úthlutað fyrir það. En fjöldi bita mantissa og veldisvísis ræðst af raunverulegri gerð. Svo, fyrir REAL gerð, er lengd mantissa 23 bitar (einni biti sem er jafn 1 er óbeint bætt við upphaf mantissa, og niðurstaðan er 24), og veldisvísirinn er 8 bitar. Samtals eru 32 bitar, eða 4 bæti. Og fyrir DOUBLE PRECISION gerðina mun lengd mantissa vera 52 bitar og veldisvísirinn verður 11 bitar, samtals 64 bitar, eða 8 bæti. PostgreSQL styður ekki meiri nákvæmni fyrir flottölur.

Við skulum pakka tugatölunni okkar 0.1 í bæði REAL og DOUBLE PRECISION gerðir. Þar sem tákn og gildi veldisvísisins eru þau sömu, munum við einbeita okkur að mantissa (ég sleppa vísvitandi óljósu eiginleikum þess að geyma gildi veldisvísisins og núll raungildi, þar sem þau flækja skilning og afvegaleiða kjarnann um vandamálið, ef þú hefur áhuga, sjáðu IEEE 754 staðalinn). Hvað munum við fá? Í efstu línunni mun ég gefa upp „mantissa“ fyrir REAL tegundina (að teknu tilliti til námundunar síðasta bita með 1 að næstu táknuðu tölu, annars verður hún 0.099999...), og í neðstu línunni - fyrir tvöfalda nákvæmni gerð:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Þetta eru greinilega tvær gjörólíkar tölur! Þess vegna, þegar borið er saman, verður fyrsta talan fyllt með núllum og verður því stærri en sú síðari (að teknu tilliti til námundunar - sú sem er feitletruð). Þetta skýrir tvíræðni frá dæmum okkar. Í öðru dæminu er beinlínis tilgreind tala 0.1 steypt í DOUBLE PRECISION gerðina og síðan borin saman við tölu af REAL gerðinni. Báðir eru minnkaðir í sömu gerð og við höfum nákvæmlega það sem við sjáum hér að ofan. Við skulum breyta fyrirspurninni þannig að allt falli á sinn stað:

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

Og reyndar, með því að framkvæma tvöfalda lækkun á tölunni 0.1 í ALVÖRU og tvöfalda nákvæmni, fáum við svarið við gátunni:

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

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

Þetta skýrir einnig þriðja dæmið hér að ofan. Talan 123 er einföld það er ómögulegt að passa mantissa í 24 bita (23 skýr + 1 gefið í skyn). Hámarks heiltala sem getur passað inn í 24 bita er 224-1 = 16. Þess vegna er talan okkar 777 námunduð að næstu táknrænu 215. Með því að breyta gerðinni í TVÖFLA NÁKVÆÐI sjáum við ekki lengur þessa atburðarás:

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

Það er allt og sumt. Það kemur í ljós að það eru engin kraftaverk. En allt sem lýst er er góð ástæða til að hugsa um hversu mikið þú þarft raunverulega alvöru týpuna. Kannski er stærsti kosturinn við notkun þess hraði útreikninga með þekktu tapi á nákvæmni. En væri þetta alhliða atburðarás sem myndi réttlæta svo tíða notkun af þessu tagi? Ekki hugsa.

Heimild: www.habr.com

Bæta við athugasemd