Nerealaj Trajtoj de Realaj Tipoj, aŭ Atentu kun REAL

Post publikigo artikoloj pri la funkcioj de tajpado en PostgreSQL, la unua komento temis pri la malfacilaĵoj labori kun realaj nombroj. Mi decidis rapide rigardi la kodon de la SQL-demandoj disponeblaj al mi por vidi kiom ofte ili uzas la REALA tipon. Montriĝas, ke ĝi estas uzata sufiĉe ofte, kaj programistoj ne ĉiam komprenas la danĝerojn malantaŭ ĝi. Kaj tio malgraŭ tio, ke estas sufiĉe multe da bonaj artikoloj en la interreto kaj en Habré pri la trajtoj de stokado de realaj nombroj en komputila memoro kaj pri laboro kun ili. Sekve, en ĉi tiu artikolo mi provos apliki tiajn funkciojn al PostgreSQL, kaj provos rapide rigardi la problemojn asociitajn kun ili, por ke estos pli facile por SQL-demandaj programistoj eviti ilin.

La PostgreSQL-dokumentado deklaras koncize: "La administrado de tiaj eraroj kaj ilia disvastigo dum komputado estas la temo de tuta branĉo de matematiko kaj komputiko, kaj ne estas kovrita ĉi tie" (dum saĝe plusendas la leganton al la IEEE 754 normo). Kiajn erarojn oni celas ĉi tie? Ni diskutu ilin en ordo, kaj baldaŭ evidentiĝos, kial mi reprenis la plumon.

Ni prenu ekzemple simplan peton:

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

Kiel rezulto, ni ne vidos ion specialan - ni ricevos la atenditan 0.1. Sed nun ni komparu ĝin kun 0.1:

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

Ne egala! Kiaj mirakloj! Sed plu, pli. Iu diros, mi scias, ke REALA kondutas malbone kun frakcioj, do mi enmetos tutajn nombrojn tie, kaj ĉio certe estos bone ĉe ili. Bone, ni ĵetu la numeron 123 al REALA:

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

Kaj ĝi rezultis esti 3 pli! Jen, la datumbazo finfine forgesis kiel kalkuli! Aŭ ĉu ni miskomprenas ion? Ni eltrovu ĝin.

Unue, ni memoru la materialon. Kiel vi scias, ajna dekuma nombro povas esti vastigita en potencojn de dek. Do, la nombro 123.456 egalos al 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ​​​​6*10-3. Sed la komputilo funkcias kun nombroj en duuma formo, tial ili devas esti reprezentitaj en formo de ekspansio en potencoj de du. Tial, la nombro 5.625 en binara estas reprezentita kiel 101.101 kaj estos egala al 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. Kaj se pozitivaj potencoj de du ĉiam donas tutajn dekumajn nombrojn (1, 2, 4, 8, 16, ktp.), tiam ĉe negativaj ĉio estas pli komplika (0.5, 0.25, 0.125, 0,0625, ktp.). La problemo estas tio Ne ĉiu decimalo povas esti reprezentita kiel finhava binara frakcio. Tiel, nia fifama 0.1 en la formo de binara frakcio aperas kiel la perioda valoro 0.0(0011). Sekve, la fina valoro de ĉi tiu nombro en komputila memoro varias depende de la bitprofundo.

Nun estas la tempo por memori kiel realaj nombroj estas konservitaj en komputila memoro. Ĝenerale parolante, reala nombro konsistas el tri ĉefaj partoj - signo, mantiso kaj eksponento. La signo povas esti aŭ plus aŭ minus, do unu bito estas asignita por ĝi. Sed la nombro da bitoj de la mantiso kaj eksponento estas determinita de la reala tipo. Do, por la REALA tipo, la longo de la mantiso estas 23 bitoj (unu bito egala al 1 estas implicite aldonita al la komenco de la mantiso, kaj la rezulto estas 24), kaj la eksponento estas 8 bitoj. La totalo estas 32 bitoj, aŭ 4 bajtoj. Kaj por la tipo DOUBLE PRECISION, la longo de la mantiso estos 52 bitoj, kaj la eksponento estos 11 bitoj, por entute 64 bitoj, aŭ 8 bajtoj. PostgreSQL ne subtenas pli altan precizecon por glitkomaj nombroj.

Ni paku nian dekuman numeron 0.1 en ambaŭ REALA kaj DUOBLA PRECIZIO-tipoj. Ĉar la signo kaj valoro de la eksponento estas la samaj, ni koncentriĝos sur la mantiso (mi intence preterlasas la ne-evidentajn trajtojn de stokado de la valoroj de la eksponento kaj nul realaj valoroj, ĉar ili malfaciligas komprenon kaj malatentigas de la esenco. de la problemo, se interesiĝas, vidu la normon IEEE 754). Kion ni ricevos? En la supra linio mi donos la "mantison" por la REALA tipo (konsiderante la rondigon de la lasta bito je 1 al la plej proksima reprezentebla nombro, alie ĝi estos 0.099999...), kaj en la malsupra linio - por la DUOBLA PRECIZIO-tipo:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Evidente ĉi tiuj estas du tute malsamaj nombroj! Sekve, dum komparo, la unua nombro estos plenigita per nuloj kaj, do, estos pli granda ol la dua (konsiderante rondigon - tiu markita en grasa skribo). Ĉi tio klarigas la ambiguecon de niaj ekzemploj. En la dua ekzemplo, la eksplicite specifita nombro 0.1 estas gisita al la DOUBLE PRECIZIO-tipo, kaj tiam komparita kun nombro de la REALA tipo. Ambaŭ estas reduktitaj al la sama tipo, kaj ni havas ĝuste tion, kion ni vidas supre. Ni modifu la demandon por ke ĉio faru lokon:

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

Kaj efektive, farante duoblan redukton de la nombro 0.1 al REALA kaj DUOBLA PRECIZIO, ni ricevas la respondon al la enigmo:

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

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

Ĉi tio ankaŭ klarigas la trian ekzemplon supre. La numero 123 estas simpla estas neeble ĝustigi la mantison en 24 bitojn (23 eksplicita + 1 subkomprenata). La maksimuma entjero, kiu povas konveni en 24 bitojn, estas 224-1 = 16 777 215. Tial nia numero 123 456 789 estas rondigita al la plej proksima reprezentebla 123 456 792. Ŝanĝante la tipon al DUOBLA PRECIZIO, ni ne plu vidas ĉi tiun scenaron:

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

Tio estas ĉio. Montriĝas, ke ne ekzistas mirakloj. Sed ĉio priskribita estas bona kialo por pensi pri kiom vi vere bezonas la VERAN tipon. Eble la plej granda avantaĝo de ĝia uzo estas la rapideco de kalkuloj kun konata perdo de precizeco. Sed ĉu ĉi tio estus universala scenaro, kiu pravigus tiel oftan uzon de ĉi tiu tipo? Ne pensu.

fonto: www.habr.com

Aldoni komenton