ویژگی های غیر واقعی انواع واقعی، یا مراقب واقعی باشید

پس از انتشار مقاله در مورد ویژگی های تایپ در 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) ظاهر می شود. در نتیجه، مقدار نهایی این عدد در حافظه کامپیوتر بسته به عمق بیت متفاوت خواهد بود.

اکنون زمان آن است که به یاد بیاوریم اعداد واقعی چگونه در حافظه کامپیوتر ذخیره می شوند. به طور کلی، یک عدد واقعی از سه بخش اصلی تشکیل شده است - علامت، مانتیس و توان. علامت می تواند مثبت یا منفی باشد، بنابراین یک بیت برای آن اختصاص داده می شود. اما تعداد بیت های آخوندک و نما با نوع واقعی تعیین می شود. بنابراین، برای نوع REAL، طول مانتیس 23 بیت است (یک بیت برابر با 1 به طور ضمنی به ابتدای مانتیس اضافه می شود و نتیجه 24 است) و توان آن 8 بیت است. کل 32 بیت یا 4 بایت است. و برای نوع DOUBLE PRECISION، طول مانتیس 52 بیت و توان 11 بیت خواهد بود که در مجموع 64 بیت یا 8 بایت خواهد بود. PostgreSQL از دقت بالاتری برای اعداد ممیز شناور پشتیبانی نمی کند.

بیایید عدد اعشاری خود را 0.1 در هر دو نوع REAL و DOUBLE PRECISION جمع کنیم. از آنجایی که علامت و مقدار توان یکسان است، ما روی آخوندک تمرکز خواهیم کرد (من عمداً ویژگی های غیر آشکار ذخیره مقادیر توان و مقادیر واقعی صفر را حذف می کنم، زیرا آنها درک را پیچیده می کنند و تمرکز را از ماهیت منحرف می کنند. از مشکل، در صورت علاقه، به استاندارد 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 و DOUBLE PRECISION، پاسخ معما را می گیریم:

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

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

این نیز مثال سوم بالا را توضیح می دهد. عدد 123 ساده است جای دادن مانتیس در 24 بیت غیرممکن است (23 صریح + 1 ضمنی). حداکثر عدد صحیحی که می تواند در 24 بیت قرار گیرد 224-1 = 16 است. بنابراین عدد 777 ما به نزدیکترین عدد قابل نمایش 215 گرد می شود. با تغییر نوع به DOUBLE PRECISION دیگر این سناریو را مشاهده نمی کنیم:

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

همین. معلوم است که هیچ معجزه ای وجود ندارد. اما همه چیزهایی که توضیح داده شد دلیل خوبی برای فکر کردن به این است که واقعاً چقدر به نوع واقعی نیاز دارید. شاید بزرگترین مزیت استفاده از آن، سرعت محاسبات با کاهش دقت شناخته شده باشد. اما آیا این یک سناریوی جهانی است که استفاده مکرر از این نوع را توجیه کند؟ فکر نکن

منبع: www.habr.com

اضافه کردن نظر