Հրապարակումից հետո
PostgreSQL փաստաթղթում հակիրճ ասվում է. «Նման սխալների կառավարումը և դրանց տարածումը հաշվման ընթացքում մաթեմատիկայի և համակարգչային գիտության մի ամբողջ ճյուղի առարկա է և չի ընդգրկված այստեղ» (միաժամանակ ընթերցողին խելամտորեն հղում է անում IEEE 754 ստանդարտին): Ինչպիսի՞ սխալների մասին է խոսքը: Քննարկենք դրանք հերթականությամբ, և շուտով պարզ կդառնա, թե ինչու նորից վերցրի գրիչը։
Եկեք օրինակ վերցնենք մի պարզ խնդրանք.
********* ЗАПРОС *********
SELECT 0.1::REAL;
**************************
float4
--------
0.1
(1 строка)
Արդյունքում մենք ոչ մի առանձնահատուկ բան չենք տեսնի. մենք կստանանք սպասված 0.1: Բայց հիմա եկեք համեմատենք այն 0.1-ի հետ.
********* ЗАПРОС *********
SELECT 0.1::REAL = 0.1;
**************************
?column?
----------
f
(1 строка)
Ոչ հավասար! Ի՜նչ հրաշքներ։ Բայց ավելին, ավելին: Ինչ-որ մեկը կասի, ես գիտեմ, որ REAL-ը իրեն վատ է պահում կոտորակների հետ, ուստի ես այնտեղ ամբողջ թվեր կմտցնեմ, և նրանց մոտ ամեն ինչ հաստատ լավ կլինի: Լավ, եկեք 123 թիվը փոխանցենք REAL-ին:
********* ЗАПРОС *********
SELECT 123456789::REAL::INT;
**************************
int4
-----------
123456792
(1 строка)
Եվ պարզվեց ևս 3-ը: Վերջ, տվյալների բազան վերջապես մոռացել է հաշվել։ Թե՞ մենք ինչ-որ բան սխալ ենք հասկանում։ Եկեք պարզենք այն:
Նախ, եկեք հիշենք նյութը. Ինչպես գիտեք, ցանկացած տասնորդական թիվ կարող է ընդլայնվել մինչև տասը: Այսպիսով, 123.456 թիվը հավասար կլինի 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + 6*10-3: Բայց համակարգիչը գործում է թվերի հետ երկուական ձևով, հետևաբար դրանք պետք է ներկայացվեն ընդլայնման ձևով երկու ուժերով: Հետևաբար, 5.625 թիվը երկուականում ներկայացված է որպես 101.101 և հավասար կլինի 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3: Եվ եթե երկուսի դրական հզորությունները միշտ տալիս են ամբողջ տասնորդական թվեր (1, 2, 4, 8, 16 և այլն), ապա բացասականների դեպքում ամեն ինչ ավելի բարդ է (0.5, 0.25, 0.125, 0,0625 և այլն)։ Խնդիրն այն է, որ Ամեն տասնորդական չէ, որ կարող է ներկայացվել որպես վերջավոր երկուական կոտորակ. Այսպիսով, մեր տխրահռչակ 0.1-ը երկուական կոտորակի տեսքով հայտնվում է որպես 0.0(0011) պարբերական արժեք: Հետևաբար, համակարգչային հիշողության մեջ այս թվի վերջնական արժեքը կտարբերվի՝ կախված բիթերի խորությունից:
Հիմա ժամանակն է հիշելու, թե ինչպես են իրական թվերը պահվում համակարգչի հիշողության մեջ: Ընդհանուր առմամբ, իրական թիվը բաղկացած է երեք հիմնական մասերից՝ նշան, մանտիսա և ցուցիչ: Նշանը կարող է լինել կամ գումարած կամ մինուս, ուստի դրա համար հատկացվում է մեկ բիթ: Բայց մանտիսայի և ցուցիչի բիթերի թիվը որոշվում է իրական տիպով: Այսպիսով, ԻՐԱԿԱՆ տիպի համար մանտիսայի երկարությունը 23 բիթ է (1-ի հավասար բիթը անուղղակիորեն ավելացվում է մանտիսայի սկզբին, և արդյունքը 24 է), իսկ ցուցիչը 8 բիթ է։ Ընդհանուրը 32 բիթ է կամ 4 բայթ: Իսկ DOUBLE PRECISION տեսակի համար մանտիսի երկարությունը կլինի 52 բիթ, իսկ ցուցիչը՝ 11 բիթ, ընդհանուր 64 բիթ կամ 8 բիթ։ PostgreSQL-ը չի ապահովում լողացող կետով թվերի ավելի բարձր ճշգրտություն:
Եկեք փաթեթավորենք մեր տասնորդական թիվը 0.1 ինչպես ԻՐԱԿԱՆ, այնպես էլ ԿՐԿՆԱԿԻ ՃՇՇՏ տիպերի: Քանի որ ցուցիչի նշանն ու արժեքը նույնն են, մենք կկենտրոնանանք մանտիսի վրա (ես միտումնավոր բաց եմ թողնում ցուցիչի և զրոյական իրական արժեքների արժեքների պահպանման ոչ ակնհայտ հատկությունները, քանի որ դրանք բարդացնում են ըմբռնումը և շեղում են էությունից: խնդրի վերաբերյալ, եթե հետաքրքրված է, տես IEEE 754 ստանդարտը): Ի՞նչ ենք ստանալու։ Վերևի տողում կտամ «մանտիսա» REAL տիպի համար (հաշվի առնելով վերջին բիթը 1-ով կլորացնելը մինչև մոտակա ներկայացվող թիվը, հակառակ դեպքում այն կլինի 0.099999...), իսկ ներքևի տողում՝ համար։ Կրկնակի Ճշգրտության տեսակը.
0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001
Ակնհայտորեն սրանք երկու բոլորովին տարբեր թվեր են: Հետևաբար, համեմատելիս առաջին թիվը կլրացվի զրոներով և, հետևաբար, ավելի մեծ կլինի, քան երկրորդը (հաշվի առնելով կլորացումը՝ թավով նշվածը): Սա բացատրում է մեր օրինակների անորոշությունը: Երկրորդ օրինակում բացահայտորեն նշված 0.1 թիվը տրված է ԿՐԿՆԱԿԻ ՃՇՇՏ տիպին, այնուհետև համեմատվում է ԻՐԱԿԱՆ տիպի մի շարքի հետ: Երկուսն էլ կրճատվել են նույն տեսակի, և մենք ունենք հենց այն, ինչ տեսնում ենք վերևում: Եկեք փոփոխենք հարցումն այնպես, որ ամեն ինչ իր տեղում լինի.
********* ЗАПРОС *********
SELECT 0.1::REAL > 0.1::DOUBLE PRECISION;
**************************
?column?
----------
t
(1 строка)
Եվ իսկապես, կատարելով 0.1 թվի կրկնակի կրճատում դեպի ԻՐԱԿԱՆ և ԿՐԿՆԱԿԻ ՃՇՇՏՈՒԹՅԱՆ, մենք ստանում ենք հանելուկի պատասխանը.
********* ЗАПРОС *********
SELECT 0.1::REAL::DOUBLE PRECISION;
**************************
float8
-------------------
0.100000001490116
(1 строка)
Սա նաև բացատրում է վերը նշված երրորդ օրինակը: 123 թիվը պարզ է անհնար է մանտիսան տեղավորել 24 բիթով (23 բացահայտ + 1 ենթադրյալ): Առավելագույն ամբողջ թիվը, որը կարող է տեղավորվել 24 բիթում, 224-1 = 16 է: Հետևաբար, մեր 777 թիվը կլորացվում է մինչև մոտակա ներկայացվող 215-ը: Փոխելով տեսակը ԿՐԿՆԱԿԻ ՃՇՇՏՈՒԹՅԱՆ, մենք այլևս չենք տեսնում այս սցենարը.
********* ЗАПРОС *********
SELECT 123456789::DOUBLE PRECISION::INT;
**************************
int4
-----------
123456789
(1 строка)
Այսքանը: Պարզվում է՝ հրաշքներ չկան։ Բայց այն ամենը, ինչ նկարագրված է, լավ առիթ է մտածելու, թե իրականում որքան է ձեզ անհրաժեշտ ԻՐԱԿԱՆ տեսակը: Թերևս դրա օգտագործման ամենամեծ առավելությունը հաշվարկների արագությունն է՝ ճշգրտության հայտնի կորստով: Բայց արդյո՞ք սա կլինի համընդհանուր սցենար, որը կարդարացներ նման հաճախակի օգտագործումը: Մի մտածիր.
Source: www.habr.com