JavaRush /وبلاگ جاوا /Random-FA /دستگاه اعداد حقیقی

دستگاه اعداد حقیقی

در گروه منتشر شد
سلام! در سخنرانی امروز ما در مورد اعداد در جاوا و به طور خاص در مورد اعداد واقعی صحبت خواهیم کرد. دستگاه اعداد واقعی - 1وحشت نکنید! :) هیچ مشکل ریاضی در سخنرانی وجود نخواهد داشت. ما در مورد اعداد واقعی منحصراً از دیدگاه "برنامه نویس" خود صحبت خواهیم کرد. بنابراین، "اعداد واقعی" چیست؟ اعداد حقیقی اعدادی هستند که دارای یک جزء کسری (که می تواند صفر باشد). آنها می توانند مثبت یا منفی باشند. در اینجا چند نمونه آورده شده است: 15 56.22 0.0 1242342343445246 -232336.11 یک عدد واقعی چگونه کار می کند؟ بسیار ساده: از یک قسمت صحیح، یک قسمت کسری و یک علامت تشکیل شده است. برای اعداد مثبت علامت معمولاً به صراحت نشان داده نمی شود، اما برای اعداد منفی نشان داده می شود. قبلاً به تفصیل بررسی کردیم که چه عملیاتی بر روی اعداد را می توان در جاوا انجام داد. در میان آنها بسیاری از عملیات ریاضی استاندارد وجود داشت - جمع، تفریق، و غیره. همچنین تعدادی عملیات جدید برای شما وجود داشت: به عنوان مثال، باقیمانده تقسیم. اما کار با اعداد دقیقاً در داخل کامپیوتر چگونه کار می کند؟ به چه صورت در حافظه ذخیره می شوند؟

ذخیره اعداد واقعی در حافظه

فکر می کنم برای شما کشفی نباشد که اعداد می توانند بزرگ و کوچک باشند :) می توان آنها را با یکدیگر مقایسه کرد. مثلا عدد 100 کمتر از عدد 423324 است آیا این روی عملکرد کامپیوتر و برنامه ما تاثیری دارد؟ در واقع - بله . هر عدد در جاوا با محدوده خاصی از مقادیر نشان داده می شود :
تایپ کنید اندازه حافظه (بیت) محدوده ارزش ها
byte 8 بیت -128 تا 127
short 16 بیت -32768 تا 32767
char 16 بیت عدد صحیح بدون علامت که نشان دهنده یک کاراکتر UTF-16 (حروف و اعداد) است.
int 32 بیت از -2147483648 تا 2147483647
long 64 بیت از -9223372036854775808 تا 9223372036854775807
float 32 بیت از 2 -149 تا (2-2 -23 )*2 127
double 64 بیت از 2 -1074 تا (2-2 -52 )*2 1023
امروز در مورد دو نوع آخر صحبت خواهیم کرد - floatو double. هر دو یک کار را انجام می دهند - نشان دهنده اعداد کسری. آنها همچنین اغلب " اعداد ممیز شناور" نامیده می شوند . این اصطلاح را برای آینده به خاطر بسپارید :) مثلا عدد 2.3333 یا 134.1212121212. خیلی عجیبه پس از همه، معلوم می شود که بین این دو نوع تفاوتی وجود ندارد، زیرا آنها یک وظیفه را انجام می دهند؟ اما یک تفاوت وجود دارد. به ستون "اندازه در حافظه" در جدول بالا توجه کنید. همه اعداد (و نه فقط اعداد - به طور کلی همه اطلاعات) در حافظه کامپیوتر به شکل بیت ذخیره می شوند. بیت کوچکترین واحد اطلاعات است. خیلی ساده است. هر بیت برابر با 0 یا 1 است. و کلمه " bit " خود از " رقم دودویی " انگلیسی - یک عدد باینری - می آید. فکر می کنم احتمالاً در مورد وجود سیستم اعداد باینری در ریاضیات شنیده اید. هر عدد اعشاری که با آن آشنا هستیم را می توان به صورت مجموعه ای از یک و صفر نشان داد. به عنوان مثال، عدد 584.32 در باینری به این صورت خواهد بود: 100100100001010001111 . هر یک و صفر در این عدد یک بیت جداگانه است. اکنون باید در مورد تفاوت بین انواع داده ها واضح تر باشید. برای مثال، اگر تعدادی از نوع ایجاد کنیم float، فقط 32 بیت در اختیار داریم. در هنگام ایجاد یک عدد، floatاین دقیقاً همان مقدار فضایی است که در حافظه کامپیوتر به آن اختصاص داده می شود. اگر بخواهیم عدد 123456789.65656565656565 را ایجاد کنیم، به صورت باینری به این صورت خواهد بود: 111010110111110011010001010110101000000 . از 38 یک و صفر تشکیل شده است، یعنی برای ذخیره آن در حافظه به 38 بیت نیاز است. این عدد به سادگی با نوع آن float"جا نمی شود" ! بنابراین می توان عدد 123456789 را به صورت یک نوع نمایش داد double. 64 بیت برای ذخیره آن اختصاص داده شده است: این برای ما مناسب است! البته محدوده مقادیر نیز مناسب خواهد بود. برای راحتی، می توانید یک عدد را به عنوان یک جعبه کوچک با سلول در نظر بگیرید. اگر سلول های کافی برای ذخیره هر بیت وجود داشته باشد، نوع داده به درستی انتخاب می شود :) دستگاه اعداد واقعی - 2البته مقادیر مختلف حافظه اختصاص داده شده نیز روی خود عدد تأثیر می گذارد. لطفاً توجه داشته باشید که انواع floatدارای doubleمحدوده های متفاوتی از مقادیر هستند. این در عمل به چه معناست؟ یک عدد doubleمی تواند دقت بیشتری نسبت به یک عدد بیان کند float. اعداد ممیز شناور 32 بیتی (در جاوا دقیقاً از این نوع هستند float) دقتی در حدود 24 بیت دارند، یعنی حدود 7 رقم اعشار. و اعداد 64 بیتی (در جاوا این نوع است double) دقتی در حدود 53 بیت دارند، یعنی تقریباً 16 رقم اعشار. در اینجا یک مثال است که این تفاوت را به خوبی نشان می دهد:
public class Main {

   public static void main(String[] args)  {

       float f = 0.0f;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}
در نتیجه چه چیزی باید به اینجا برسیم؟ به نظر می رسد که همه چیز بسیار ساده است. عدد 0.0 را داریم و 7 بار متوالی 0.1111111111111111 را به آن اضافه می کنیم. نتیجه باید 0.7777777777777777 باشد. اما ما یک عدد ایجاد کردیم float. اندازه آن به 32 بیت محدود شده است و همانطور که قبلاً گفتیم قادر است عددی را تا حدود 7 اعشار نمایش دهد. بنابراین، در نهایت، نتیجه ای که در کنسول به دست می آوریم با آنچه انتظار داشتیم متفاوت خواهد بود:

0.7777778
به نظر می رسید این شماره "قطع" شده بود. شما از قبل می دانید که چگونه داده ها در حافظه ذخیره می شوند - به شکل بیت، بنابراین این نباید شما را شگفت زده کند. واضح است که چرا این اتفاق افتاد: نتیجه 0.777777777777777 به سادگی در 32 بیت اختصاص داده شده به ما قرار نمی گرفت، بنابراین برای جا افتادن در متغیر type کوتاه شد float:) ما می توانیم نوع متغیر را doubleدر مثال خود و سپس نهایی را تغییر دهیم. نتیجه کوتاه نخواهد شد:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 7; i++) {
           f += 0.1111111111111111;
       }

       System.out.println(f);
   }
}

0.7777777777777779
در حال حاضر 16 رقم اعشار وجود دارد، نتیجه به 64 بیت می رسد. به هر حال، شاید متوجه شدید که در هر دو مورد نتایج کاملاً صحیح نبود؟ محاسبه با اشتباهات جزئی انجام شد. در ادامه در مورد دلایل این موضوع صحبت خواهیم کرد :) حالا چند کلمه در مورد اینکه چگونه می توانید اعداد را با یکدیگر مقایسه کنید، بگوییم.

مقایسه اعداد حقیقی

ما قبلاً در آخرین سخنرانی که در مورد عملیات مقایسه صحبت کردیم تا حدی به این موضوع پرداختیم. ما عملیات هایی مانند >, <, , را دوباره آنالیز نخواهیم کرد >=. <=در عوض به مثال جالب تری نگاه می کنیم:
public class Main {

   public static void main(String[] args)  {

       double f = 0.0;
       for (int i=1; i <= 10; i++) {
           f += 0.1;
       }

       System.out.println(f);
   }
}
به نظر شما چه عددی روی صفحه نمایش داده می شود؟ پاسخ منطقی این خواهد بود: عدد 1. شمارش را از عدد 0.0 شروع می کنیم و ده بار پشت سر هم 0.1 را به آن اضافه می کنیم. به نظر می رسد همه چیز درست است، باید یکی باشد. این کد را اجرا کنید، پاسخ شما را بسیار شگفت زده خواهد کرد :) خروجی کنسول:

0.9999999999999999
اما چرا در یک مثال ساده خطایی رخ داد؟ O_o در اینجا حتی یک دانش آموز کلاس پنجم به راحتی می تواند پاسخ صحیح بدهد، اما برنامه جاوا نتیجه نادرستی ایجاد کرد. «نادرست» در اینجا کلمه بهتری از «نادرست» است. ما هنوز یک عدد بسیار نزدیک به یک داریم، و نه فقط مقداری تصادفی :) این عدد با عدد صحیح به معنای واقعی کلمه یک میلی متر تفاوت دارد. اما چرا؟ شاید این فقط یک اشتباه یک بار باشد. شاید کامپیوتر خراب شده؟ بیایید سعی کنیم مثال دیگری بنویسیم.
public class Main {

   public static void main(String[] args)  {

       //add 0.1 to zero eleven times in a row
       double f1 = 0.0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = 0.1 * 11;

       //should be the same - 1.1 in both cases
       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       // Let's check!
       if (f1 == f2)
           System.out.println("f1 and f2 are equal!");
       else
           System.out.println("f1 and f2 are not equal!");
   }
}
خروجی کنسول:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
بنابراین، واضح است که این موضوع مربوط به اشکالات رایانه ای نیست :) چه خبر است؟ این نوع خطاها مربوط به نحوه نمایش اعداد به صورت باینری در حافظه کامپیوتر است. واقعیت این است که در سیستم باینری نمی توان به طور دقیق عدد 0.1 را نشان داد . به هر حال، سیستم اعشاری نیز مشکل مشابهی دارد: نمایش صحیح کسرها غیرممکن است (و به جای ⅓ ما 0.333333333333333 ... را دریافت می کنیم، که همچنین نتیجه کاملاً صحیح نیست). به نظر یک چیز کوچک است: با چنین محاسباتی، تفاوت می تواند صد هزارمین قسمت (0.00001) یا حتی کمتر باشد. اما اگر کل نتیجه برنامه خیلی جدی شما به این مقایسه بستگی داشته باشد چه؟
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
ما به وضوح انتظار داشتیم که این دو عدد برابر باشند، اما به دلیل طراحی حافظه داخلی، پرتاب موشک را لغو کردیم. دستگاه اعداد واقعی - 3اگر چنین است، باید تصمیم بگیریم که چگونه دو عدد ممیز شناور را با هم مقایسه کنیم تا نتیجه مقایسه بیشتر... امم... قابل پیش بینی باشد. بنابراین، ما قبلاً قانون شماره 1 را هنگام مقایسه اعداد واقعی یاد گرفته ایم: هرگز از اعداد ممیز شناور هنگام مقایسه اعداد واقعی استفاده نکنید . == اوکی، فکر کنم به اندازه کافی مثال بد باشه :) یه مثال خوب ببینیم!
public class Main {

   public static void main(String[] args)  {

       final double threshold = 0.0001;

       //add 0.1 to zero eleven times in a row
       double f1 = .0;
       for (int i = 1; i <= 11; i++) {
           f1 += .1;
       }

       // Multiply 0.1 by 11
       double f2 = .1 * 11;

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       if (Math.abs(f1 - f2) < threshold)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
در اینجا ما اساساً همان کار را انجام می دهیم، اما نحوه مقایسه اعداد را تغییر می دهیم. ما یک عدد "آستانه" ویژه داریم - 0.0001، یک ده هزارم. ممکن است متفاوت باشد. بستگی به این دارد که در یک مورد خاص چقدر به مقایسه دقیقی نیاز دارید. می توانید آن را بزرگتر یا کوچکتر کنید. با استفاده از روش Math.abs()مدول یک عدد را بدست می آوریم. مدول مقدار یک عدد بدون توجه به علامت است. به عنوان مثال اعداد -5 و 5 مدول یکسانی خواهند داشت و برابر با 5 خواهند بود. عدد دوم را از عدد اول کم می کنیم و اگر نتیجه حاصل بدون توجه به علامت کمتر از آستانه ای باشد که تعیین می کنیم تعداد ما برابر است در هر صورت، آنها برابر با درجه دقتی هستند که ما با استفاده از "عدد آستانه" خود تعیین کردیم ، یعنی حداقل آنها برابر با یک ده هزارم هستند. این روش مقایسه شما را از رفتار غیرمنتظره ای که در مورد ==. یک راه خوب دیگر برای مقایسه اعداد واقعی استفاده از یک کلاس ویژه است BigDecimal. این کلاس به طور خاص برای ذخیره اعداد بسیار بزرگ با یک قسمت کسری ایجاد شده است. بر خلاف doubleو float، هنگام استفاده از BigDecimalجمع، تفریق و سایر عملیات های ریاضی نه با استفاده از عملگرها (و +-غیره)، بلکه با استفاده از روش ها انجام می شود. در مورد ما به این صورت خواهد بود:
import java.math.BigDecimal;

public class Main {

   public static void main(String[] args)  {

       /*Create two BigDecimal objects - zero and 0.1.
       We do the same thing as before - add 0.1 to zero 11 times in a row
       In the BigDecimal class, addition is done using the add () method */
       BigDecimal f1 = new BigDecimal(0.0);
       BigDecimal pointOne = new BigDecimal(0.1);
       for (int i = 1; i <= 11; i++) {
           f1 = f1.add(pointOne);
       }

       /*Nothing has changed here either: create two BigDecimal objects
       and multiply 0.1 by 11
       In the BigDecimal class, multiplication is done using the multiply() method*/
       BigDecimal f2 = new BigDecimal(0.1);
       BigDecimal eleven = new BigDecimal(11);
       f2 = f2.multiply(eleven);

       System.out.println("f1 = " + f1);
       System.out.println("f2 = " + f2);

       /*Another feature of BigDecimal is that number objects need to be compared with each other
       using the special compareTo() method*/
       if (f1.compareTo(f2) == 0)
           System.out.println("f1 and f2 are equal");
       else
           System.out.println("f1 and f2 are not equal");
   }
}
چه نوع خروجی کنسولی خواهیم داشت؟

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
ما دقیقاً به نتیجه ای که انتظار داشتیم رسیدیم. و توجه کنید که اعداد ما چقدر دقیق هستند و چند رقم اعشار در آنها قرار می گیرند! خیلی بیشتر از در floatو حتی در double! کلاس را BigDecimalبرای آینده به خاطر بسپارید، حتماً به آن نیاز خواهید داشت :) فیو! سخنرانی بسیار طولانی بود، اما شما آن را انجام دادید: آفرین! :) در درس بعدی می بینمت، برنامه نویس آینده!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION