Нақты түрлердің шынайы емес мүмкіндіктері немесе REAL-мен абай болыңыз

Жарияланғаннан кейін мақалалар PostgreSQL-де теру мүмкіндіктері туралы бірінші пікір нақты сандармен жұмыс істеу қиындықтары туралы болды. Мен олардың REAL түрін қаншалықты жиі қолданатынын көру үшін қол жетімді SQL сұрауларының кодын жылдам қарап шығуды шештім. Ол өте жиі қолданылады және әзірлеушілер оның астарында қандай қауіп бар екенін әрқашан түсіне бермейді. Бұл Интернетте және Хабреде нақты сандарды компьютер жадында сақтау және олармен жұмыс істеу туралы көптеген жақсы мақалалар бар екеніне қарамастан. Сондықтан, осы мақалада мен осындай мүмкіндіктерді 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 бөлшекпен нашар әрекет ететінін білемін, сондықтан мен онда бүтін сандарды енгіземін, олармен бәрі жақсы болады. Жарайды, REAL-ға 123 456 789 санын шығарайық:

********* ЗАПРОС *********
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) ретінде пайда болады. Демек, бұл санның компьютер жадындағы соңғы мәні бит тереңдігіне байланысты өзгереді.

Енді нақты сандар компьютер жадында қалай сақталатынын еске түсіретін кез келді. Жалпы айтқанда, нақты сан үш негізгі бөліктен тұрады - белгі, мантисса және көрсеткіш. Таңба плюс немесе минус болуы мүмкін, сондықтан оған бір бит бөлінеді. Бірақ мантисса мен көрсеткіштің биттерінің саны нақты типпен анықталады. Сонымен, REAL түрі үшін мантиссаның ұзындығы 23 бит (мантиссаның басына 1-ге тең бір бит жанама түрде қосылады, ал нәтиже 24), ал дәреже көрсеткіші 8 бит. Барлығы 32 бит немесе 4 байт. Ал DOUBLE PRECISION түрі үшін мантисса ұзындығы 52 бит, ал дәреже көрсеткіші 11 бит, жалпы саны 64 бит немесе 8 байт болады. PostgreSQL өзгермелі нүкте сандары үшін жоғары дәлдікті қолдамайды.

0.1 ондық санымызды REAL және ҚОС ДАЛДЫҚ түрлеріне жинақтайық. Көрсеткіштің таңбасы мен мәні бірдей болғандықтан, біз мантиссаға тоқталамыз (көрсеткіш мәндерін және нөлдік нақты мәндерді сақтаудың айқын емес ерекшеліктерін әдейі өткізіп жіберемін, өйткені олар түсінуді қиындатады және мәннен алшақтатады. мәселе туралы, егер сізді қызықтырса, IEEE 754 стандартын қараңыз). Біз не аламыз? Жоғарғы жолда REAL түрі үшін «мантиссаны» беремін (соңғы биттің 1-ге жуықталатын санға дейін дөңгелектенуін ескере отырып, әйтпесе ол 0.099999 болады...), ал төменгі жолда - үшін ҚОС ДАЛДЫҚ түрі:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Бұл екі мүлдем басқа сандар екені анық! Сондықтан салыстыру кезінде бірінші сан нөлдермен толтырылады, демек, екіншісінен үлкен болады (дөңгелектеуді ескере отырып - қою шрифтпен белгіленген). Бұл біздің мысалдарымыздағы екіұштылықты түсіндіреді. Екінші мысалда анық көрсетілген 0.1 саны DOUBLE PRECISION түріне шығарылады, содан кейін REAL түрінің санымен салыстырылады. Екеуі де бірдей түрге қысқартылған және бізде жоғарыда көргеніміз бар. Барлығы орнына келетіндей сұрауды өзгертейік:

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

Шынында да, 0.1 санын REAL және ҚОС ДӘЛДІКке дейін екі есе азайту арқылы біз жұмбақтың жауабын аламыз:

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

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

Бұл да жоғарыдағы үшінші мысалды түсіндіреді. 123 456 789 саны қарапайым мантиссаны 24 битке сыйғызу мүмкін емес (23 айқын + 1 жанама). 24 битке сыйатын максималды бүтін сан 224-1 = 16 777 215. Сондықтан біздің 123 456 789 саны ең жақын 123 456 792 өрнектелетінге дейін дөңгелектенеді. Түрді DOUBLE PRECISION түріне өзгерту арқылы біз енді бұл сценарийді көрмейміз:

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

Осымен болды. Ешқандай ғажайыптар жоқ екен. Бірақ сипатталғанның бәрі сізге REAL түрінің қаншалықты қажет екендігі туралы ойлануға жақсы себеп болып табылады. Мүмкін, оны пайдаланудың ең үлкен артықшылығы - белгілі жоғалтумен есептеулердің жылдамдығы. Бірақ бұл осы түрді жиі пайдалануды ақтайтын әмбебап сценарий бола ма? Олай ойламаймын.

Ақпарат көзі: www.habr.com

пікір қалдыру