Իրական տեսակների անիրական առանձնահատկություններ կամ զգույշ եղեք REAL-ի հետ

Հրապարակումից հետո Հոդված PostgreSQL-ում մուտքագրելու առանձնահատկությունների մասին, հենց առաջին մեկնաբանությունը վերաբերում էր իրական թվերի հետ աշխատելու դժվարություններին։ Ես որոշեցի արագ նայել ինձ հասանելի SQL հարցումների կոդը՝ տեսնելու, թե որքան հաճախ են նրանք օգտագործում REAL տեսակը: Պարզվում է, որ այն բավականին հաճախ է օգտագործվում, և մշակողները միշտ չէ, որ հասկանում են դրա հետևում գտնվող վտանգները։ Եվ սա չնայած այն հանգամանքին, որ ինտերնետում և Habré-ում կան բավականին շատ լավ հոդվածներ համակարգչի հիշողության մեջ իրական թվեր պահելու և դրանց հետ աշխատելու առանձնահատկությունների մասին: Հետևաբար, այս հոդվածում ես կփորձեմ կիրառել նման հնարավորություններ PostgreSQL-ի վրա և կփորձեմ արագ նայել դրանց հետ կապված խնդիրներին, որպեսզի SQL հարցումների մշակողների համար ավելի հեշտ լինի խուսափել դրանցից։

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

Добавить комментарий