JavaRush /Java blogi /Random-UZ /Haqiqiy sonlar qurilmasi

Haqiqiy sonlar qurilmasi

Guruhda nashr etilgan
Salom! Bugungi ma'ruzamizda biz Java tilidagi raqamlar haqida, xususan, haqiqiy sonlar haqida gaplashamiz. Haqiqiy sonlar qurilmasi - 1Vahimaga tushma! :) Ma'ruzada matematik qiyinchiliklar bo'lmaydi. Biz haqiqiy raqamlar haqida faqat bizning "dasturchimiz" nuqtai nazaridan gaplashamiz. Xo'sh, "haqiqiy raqamlar" nima? Haqiqiy sonlar - kasr qismi bo'lgan raqamlar (nolga teng bo'lishi mumkin). Ular ijobiy yoki salbiy bo'lishi mumkin. Mana bir nechta misollar: 15 56,22 0,0 1242342343445246 -232336,11 Haqiqiy son qanday ishlaydi? Juda oddiy: u butun son, kasr qism va belgidan iborat. Ijobiy raqamlar uchun belgi odatda aniq ko'rsatilmaydi, ammo salbiy raqamlar uchun u ko'rsatiladi. Ilgari biz Java-da raqamlar ustida qanday amallarni bajarish mumkinligini batafsil ko'rib chiqdik . Ular orasida ko'plab standart matematik amallar bor edi - qo'shish, ayirish va hokazo. Siz uchun yangilari ham bor edi: masalan, bo'linishning qolgan qismi. Ammo raqamlar bilan ishlash kompyuterda qanday ishlaydi? Ular xotirada qanday shaklda saqlanadi?

Haqiqiy raqamlarni xotirada saqlash

O'ylaymanki, siz uchun raqamlar katta va kichik bo'lishi mumkin bo'lgan kashfiyot bo'lmaydi :) Ularni bir-biri bilan solishtirish mumkin. Masalan, 100 soni 423324 raqamidan kichik. Bu kompyuter va dasturimizning ishlashiga ta'sir qiladimi? Aslida - ha . Har bir raqam Java'da ma'lum qiymatlar diapazoni bilan ifodalanadi :
Turi Xotira hajmi (bit) Qiymatlar diapazoni
byte 8 bit -128 dan 127 gacha
short 16 bit -32768 dan 32767 gacha
char 16 bit UTF-16 belgisini ifodalovchi belgisiz butun son (harflar va raqamlar)
int 32 bit -2147483648 dan 2147483647 gacha
long 64 bit -9223372036854775808 dan 9223372036854775807 gacha
float 32 bit 2 -149 dan (2-2 -23 )*2 127 gacha
double 64 bit 2 -1074 dan (2-2 -52 )*2 1023 gacha
Bugun biz oxirgi ikki tur haqida gapiramiz - floatva double. Ikkalasi ham bir xil vazifani bajaradi - kasr sonlarni ifodalaydi. Ular ko'pincha " suzuvchi nuqta raqamlari" deb ham ataladi . Kelajak uchun bu atamani eslab qoling :) Masalan, 2.3333 yoki 134.1212121212 raqami. Juda g'alati. Axir, bu ikki tur o'rtasida farq yo'q, chunki ular bir xil vazifani bajaradilar? Lekin farq bor. Yuqoridagi jadvaldagi "xotiradagi o'lcham" ustuniga e'tibor bering. Barcha raqamlar (faqat raqamlar emas - umuman olganda barcha ma'lumotlar) kompyuter xotirasida bit shaklida saqlanadi. Bit axborotning eng kichik birligidir. Bu juda oddiy. Har qanday bit 0 yoki 1 ga teng. “ Bit ” soʻzining oʻzi esa inglizcha “ binary digit ” – ikkilik raqamdan olingan. O'ylaymanki, siz matematikada ikkilik sanoq tizimining mavjudligi haqida eshitgan bo'lsangiz kerak. Bizga tanish bo'lgan har qanday o'nlik sonlar birliklar va nollar to'plami sifatida ifodalanishi mumkin. Masalan, ikkilik tizimda 584.32 raqami quyidagicha ko'rinadi: 100100100001010001111 . Bu raqamdagi har bir va nol alohida bitdir. Endi siz ma'lumotlar turlari o'rtasidagi farq haqida aniqroq bo'lishingiz kerak. Misol uchun, agar biz bir nechta turdagi yaratsak float, bizning ixtiyorimizda faqat 32 bit mavjud. Raqam yaratishda floatkompyuter xotirasida uning uchun qancha joy ajratiladi. Agar biz 123456789.65656565656565 raqamini yaratmoqchi bo'lsak, binarda u quyidagicha ko'rinadi: 11101011011110011010001010110101000000 . U 38 ta birlik va noldan iborat, ya'ni uni xotirada saqlash uchun 38 bit kerak bo'ladi. Bu raqam oddiygina turga float"mos kelmaydi" ! Shuning uchun 123456789 raqamini tur sifatida ko'rsatish mumkin double. Uni saqlash uchun 64 bit ajratilgan: bu bizga mos keladi! Albatta, qiymatlar oralig'i ham mos keladi. Qulaylik uchun siz raqamni katakchali kichik quti deb hisoblashingiz mumkin. Har bir bitni saqlash uchun hujayralar etarli bo'lsa, u holda ma'lumotlar turi to'g'ri tanlangan :) Haqiqiy sonlar qurilmasi - 2Albatta, ajratilgan xotiraning turli miqdori ham raqamning o'ziga ta'sir qiladi. E'tibor bering, turlar turli xil qiymatlarga floatega . doubleBu amalda nimani anglatadi? Raqam doubleraqamdan kattaroq aniqlikni ifodalashi mumkin float. 32-bitli suzuvchi nuqta raqamlari (Java-da bu aynan shunday turdagi float) aniqligi taxminan 24 bit, ya'ni taxminan 7 kasrli kasrga ega. Va 64-bitli raqamlar (Java-da bu turdagi double) taxminan 53 bit aniqlikka ega, ya'ni taxminan 16 kasr. Mana bu farqni yaxshi ko'rsatadigan misol:
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);
   }
}
Natijada nima qilishimiz kerak? Ko'rinishidan, hamma narsa juda oddiy. Bizda 0,0 raqami bor va biz unga ketma-ket 7 marta 0,1111111111111111 qo'shamiz. Natija 0,777777777777777 bo'lishi kerak. Lekin biz raqam yaratdik float. Uning o'lchami 32 bit bilan cheklangan va yuqorida aytib o'tganimizdek, u taxminan 7 kasrgacha bo'lgan raqamni ko'rsatishga qodir. Natijada, konsolda biz kutgan natijadan farq qiladi:

0.7777778
Raqam "kesilgan"ga o'xshardi. Siz allaqachon ma'lumotlarning xotirada qanday saqlanishini bilasiz - bitlar shaklida, shuning uchun bu sizni ajablantirmasligi kerak. Nima uchun bu sodir bo'lganligi aniq: natija 0,777777777777777 shunchaki bizga ajratilgan 32 bitga to'g'ri kelmadi, shuning uchun u turdagi o'zgaruvchiga mos kelishi uchun qisqartirildi float:) Biz o'zgaruvchining turini doublemisolimizda o'zgartirishimiz mumkin, keyin esa yakuniy natija qisqartirilmaydi:
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
Allaqachon 16 kasrli kasr mavjud, natija 64 bitga "mos keladi". Aytgancha, ehtimol siz ikkala holatda ham natijalar to'liq to'g'ri emasligini payqadingizmi? Hisoblash kichik xatolar bilan amalga oshirildi. Buning sabablari haqida quyida gaplashamiz :) Endi raqamlarni bir-biri bilan qanday solishtirish mumkinligi haqida bir necha so'z aytaylik.

Haqiqiy sonlarni taqqoslash

So'nggi ma'ruzada, taqqoslash operatsiyalari haqida gapirganda, biz bu masalaga qisman to'xtalib o'tdik. >Biz , <, kabi operatsiyalarni qayta tahlil qilmaymiz >=. <=Buning o'rniga qiziqroq misolni ko'rib chiqaylik:
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);
   }
}
Sizningcha, ekranda qaysi raqam ko'rsatiladi? Mantiqiy javob javob bo'ladi: 1 raqami. Biz 0,0 raqamidan hisoblashni boshlaymiz va unga ketma-ket o'n marta 0,1 qo'shamiz. Hammasi to'g'ri ko'rinadi, u bitta bo'lishi kerak. Ushbu kodni ishga tushirishga harakat qiling va javob sizni hayratda qoldiradi :) Konsol chiqishi:

0.9999999999999999
Lekin nima uchun bunday oddiy misolda xatolik yuz berdi? O_o Bu yerda hatto beshinchi sinf o‘quvchisi ham bemalol to‘g‘ri javob bera olardi, lekin Java dasturi noto‘g‘ri natija berdi. Bu erda "noto'g'ri" so'z "noto'g'ri" so'zidir. Biz hali ham tasodifiy qiymatni emas, balki bittaga yaqin raqamni oldik :) Bu to'g'ri raqamdan tom ma'noda bir millimetr bilan farq qiladi. Lekin nima uchun? Ehtimol, bu bir martalik xatodir. Ehtimol, kompyuter ishdan chiqqandir? Keling, yana bir misol yozishga harakat qilaylik.
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!");
   }
}
Konsol chiqishi:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
Demak, bu kompyuterdagi nosozliklar masalasi emas :) Nima bo'lyapti? Bu kabi xatolar sonlarning kompyuter xotirasida ikkilik ko‘rinishda ifodalanishi bilan bog‘liq. Gap shundaki, ikkilik tizimda 0,1 raqamini aniq ifodalash mumkin emas . Aytgancha, o'nli tizimda ham shunga o'xshash muammo bor: kasrlarni to'g'ri ifodalash mumkin emas (va ⅓ o'rniga biz 0,333333333333333... ni olamiz, bu ham unchalik to'g'ri natija emas). Bu arzimas narsaga o'xshaydi: bunday hisob-kitoblar bilan farq yuz mingdan bir qism (0,00001) yoki undan ham kamroq bo'lishi mumkin. Ammo juda jiddiy dasturingizning butun natijasi ushbu taqqoslashga bog'liq bo'lsa-chi?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
Biz ikkita raqam teng bo'lishini aniq kutgandik, ammo ichki xotira dizayni tufayli biz raketani uchirishni bekor qildik. Haqiqiy sonlar qurilmasi - 3Agar shunday bo'lsa, taqqoslash natijasi ko'proq... hmm... bashorat qilish mumkin bo'lishi uchun ikkita suzuvchi nuqtali sonni qanday solishtirishni hal qilishimiz kerak. Shunday qilib, biz haqiqiy sonlarni taqqoslashda 1-qoidani allaqachon bilib oldik: haqiqiy sonlarni taqqoslashda hech qachon suzuvchi nuqtali raqamlardan foydalanmang . == OK, menimcha, yomon misollar yetarli :) Keling, yaxshi misolni ko'rib chiqaylik!
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");
   }
}
Bu erda biz xuddi shu narsani qilamiz, lekin raqamlarni solishtirish usulini o'zgartiramiz. Bizda maxsus "eshik" raqami bor - 0,0001, o'ndan mingdan biri. Bu boshqacha bo'lishi mumkin. Bu muayyan holatda qanchalik aniq taqqoslash kerakligiga bog'liq. Siz uni kattaroq yoki kichikroq qilishingiz mumkin. Usul yordamida Math.abs()biz sonning modulini olamiz. Modul - bu belgidan qat'i nazar, raqamning qiymati. Misol uchun, -5 va 5 raqamlari bir xil modulga ega bo'ladi va 5 ga teng bo'ladi. Birinchisidan ikkinchi raqamni ayiramiz va agar natija belgidan qat'i nazar, biz o'rnatgan chegaradan kichik bo'lsa, u holda raqamlarimiz teng. Qanday bo'lmasin, ular bizning "bo'sagi raqamimiz" yordamida o'rnatgan aniqlik darajasiga teng , ya'ni ular kamida o'n mingdan biriga teng. Taqqoslashning ushbu usuli sizni biz ko'rgan kutilmagan xatti-harakatlardan qutqaradi ==. Haqiqiy raqamlarni solishtirishning yana bir yaxshi usuli maxsus sinfdan foydalanishdir BigDecimal. Bu sinf kasr qismi bilan juda katta sonlarni saqlash uchun maxsus yaratilgan. doubleVa dan farqli o'laroq , qo'shishni floatqo'llashda ayirish va boshqa matematik amallar operatorlar ( , va hokazo) yordamida emas, balki usullar yordamida BigDecimalamalga oshiriladi . +-Bizning holatimizda u shunday ko'rinadi:
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");
   }
}
Biz qanday konsol chiqishini olamiz?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
Aynan biz kutgan natijaga erishdik. Va bizning raqamlarimiz qanchalik to'g'ri bo'lganiga va ularga qancha kasrli kasrlar to'g'ri kelishiga e'tibor bering! floatIchkaridan va hattokidan ham ko'proq double! BigDecimalKelajak uchun sinfni eslab qoling , sizga albatta kerak bo'ladi :) Phew! Ma'ruza juda uzoq edi, lekin siz buni qildingiz: yaxshi! :) Keyingi darsda ko'rishguncha, bo'lajak dasturchi!
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION