JavaRush /Java Blogu /Random-AZ /Həqiqi ədədlər cihazı

Həqiqi ədədlər cihazı

Qrupda dərc edilmişdir
Salam! Bugünkü mühazirəmizdə Java-da rəqəmlər və konkret olaraq real ədədlər haqqında danışacağıq. Həqiqi ədədlər cihazı - 1Təlaşlanmayın! :) Mühazirədə heç bir riyazi çətinlik olmayacaq. Həqiqi rəqəmlər haqqında yalnız "proqramçı" nöqteyi-nəzərimizdən danışacağıq. Beləliklə, "həqiqi rəqəmlər" nədir? Həqiqi ədədlər kəsr hissəsi olan ədədlərdir (sıfır ola bilər). Onlar müsbət və ya mənfi ola bilər. Budur bəzi nümunələr: 15 56.22 0.0 1242342343445246 -232336.11 Həqiqi ədəd necə işləyir? Olduqca sadə: tam hissədən, kəsr hissədən və işarədən ibarətdir. Müsbət ədədlər üçün işarə adətən açıq şəkildə göstərilmir, mənfi ədədlər üçün isə göstərilir. Əvvəllər Java-da rəqəmlər üzərində hansı əməliyyatların edilə biləcəyini ətraflı araşdırdıq . Onların arasında bir çox standart riyazi əməliyyatlar var idi - toplama, çıxma və s. Sizin üçün yeniləri də var idi: məsələn, bölmənin qalan hissəsi. Bəs rəqəmlərlə işləmək kompüterin daxilində necə işləyir? Onlar yaddaşda hansı formada saxlanılır?

Real ədədlərin yaddaşda saxlanması

Düşünürəm ki, rəqəmlərin böyük və kiçik ola biləcəyi sizin üçün kəşf olmayacaq :) Onları bir-biri ilə müqayisə etmək olar. Məsələn, 100 rəqəmi 423324 rəqəmindən kiçikdir.Bu kompüterin və proqramımızın işinə təsir edirmi? Əslində - bəli . Hər bir nömrə Java-da müəyyən dəyər diapazonu ilə təmsil olunur :
Növ Yaddaş ölçüsü (bit) Dəyərlər diapazonu
byte 8 bit -128-dən 127-yə qədər
short 16 bit -32768-dən 32767-yə qədər
char 16 bit UTF-16 simvolunu təmsil edən işarəsiz tam ədəd (hərflər və rəqəmlər)
int 32 bit -2147483648-dən 2147483647-yə qədər
long 64 bit -9223372036854775808-dən 9223372036854775807-ə qədər
float 32 bit 2 -149- dan (2-2 -23 )*2 127- ə qədər
double 64 bit 2 -1074 -dən (2-2 -52 )*2 1023-ə qədər
Bu gün son iki növ haqqında danışacağıq - floatdouble. Hər ikisi eyni vəzifəni yerinə yetirir - kəsr ədədləri təmsil edir. Onlara çox vaxt “ üzən nöqtəli nömrələr” də deyilir . Gələcək üçün bu termini xatırlayın :) Məsələn, 2.3333 və ya 134.1212121212. Olduqca qəribə. Axı, belə çıxır ki, bu iki növ arasında heç bir fərq yoxdur, çünki eyni vəzifəni yerinə yetirirlər? Amma bir fərq var. Yuxarıdakı cədvəldəki “yaddaşdakı ölçü” sütununa diqqət yetirin. Bütün nömrələr (yalnız rəqəmlər deyil - ümumilikdə bütün məlumatlar) kompüterin yaddaşında bit şəklində saxlanılır. Bit ən kiçik məlumat vahididir. Bu olduqca sadədir. İstənilən bit ya 0, ya da 1-ə bərabərdir. Və “ bit ” sözünün özü də ingiliscə “ binary digit ” – ikili rəqəmdən gəlir. Düşünürəm ki, riyaziyyatda ikili say sisteminin mövcudluğu haqqında yəqin ki, eşitmisiniz. Bizə tanış olan istənilən onluq ədədi birlər və sıfırlar dəsti kimi təqdim etmək olar. Məsələn, ikilik sistemdə 584.32 rəqəmi belə görünür: 100100100001010001111 . Bu ədəddəki hər biri və sıfır ayrı bir bitdir. İndi məlumat növləri arasındakı fərq haqqında daha aydın olmalısınız. Məsələn, bir sıra tip yaratsaq float, ixtiyarımızda yalnız 32 bit olur. Nömrə yaratarkən, floatkompüterin yaddaşında onun üçün nə qədər yer ayrılacağı dəqiqdir. 123456789.65656565656565 rəqəmini yaratmaq istəsək, binar sistemdə bu belə görünəcək: 11101011011110011010001010110101000000 . 38 bir və sıfırdan ibarətdir, yəni onu yaddaşda saxlamaq üçün 38 bit lazımdır. Bu nömrə sadəcə olaraq tipə float"uymaz" ! Buna görə də, 123456789 nömrəsi bir növ kimi göstərilə bilər double. Onu saxlamaq üçün 64 bit ayrılıb: bu bizə uyğundur! Əlbəttə ki, dəyərlər diapazonu da uyğun olacaq. Rahatlıq üçün rəqəmi hüceyrələri olan kiçik bir qutu kimi düşünə bilərsiniz. Əgər hər biti saxlamaq üçün kifayət qədər xana varsa, o zaman məlumat tipi düzgün seçilir :) Həqiqi ədədlər cihazı - 2Təbii ki, ayrılmış yaddaşın müxtəlif həcmləri də nömrənin özünə təsir edir. Nəzərə alın ki, növlərin müxtəlif dəyər diapazonları floatvar . doubleBu praktikada nə deməkdir? Ədəd doublerəqəmdən daha çox dəqiqliyi ifadə edə bilər float. 32 bitlik üzən nöqtə nömrələri (Java-da bu, tam olaraq belədir float) təxminən 24 bit, yəni təxminən 7 onluq dəqiqliyə malikdir. Və 64 bitlik ədədlər (Java-da bu növüdür double) təxminən 53 bit, yəni təxminən 16 onluq dəqiqliyə malikdir. Bu fərqi yaxşı göstərən bir nümunə:
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);
   }
}
Nəticədə burada nə əldə etməliyik? Deyəsən, hər şey olduqca sadədir. Bizdə 0,0 rəqəmi var və ona ardıcıl 7 dəfə 0,1111111111111111 əlavə edirik. Nəticə 0,777777777777777 olmalıdır. Ancaq bir nömrə yaratdıq float. Onun ölçüsü 32 bitlə məhdudlaşır və əvvəllər dediyimiz kimi, təxminən 7-ci onluq yerlərə qədər rəqəmi göstərə bilir. Buna görə də, sonda konsolda əldə etdiyimiz nəticə gözlədiyimizdən fərqli olacaq:

0.7777778
Nömrə sanki “kəsilib”. Siz artıq məlumatların yaddaşda necə saxlandığını bilirsiniz - bitlər şəklində, buna görə də bu sizi təəccübləndirməməlidir. Bunun niyə baş verdiyi aydındır: nəticə 0.777777777777777 sadəcə olaraq bizə ayrılan 32 bitə sığmadı, ona görə də tip dəyişəninə sığdırmaq üçün kəsildi :) Biz nümunəmizdə floatdəyişənin tipini dəyişə bilərik , sonra isə son doublenəticə kəsilməyəcək:
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
Artıq 16 onluq yer var, nəticə 64 bitə "uyğundur". Yeri gəlmişkən, hər iki halda nəticələrin tamamilə düzgün olmadığını görmüsünüz? Hesablama kiçik səhvlərlə aparılmışdır. Bunun səbəblərindən aşağıda danışacağıq :) İndi ədədləri bir-birinizlə necə müqayisə edə biləcəyiniz haqqında bir neçə kəlmə deyək.

Həqiqi ədədlərin müqayisəsi

Ötən mühazirəmizdə müqayisə əməliyyatları haqqında danışarkən bu məsələyə artıq qismən toxunmuşduq. >, <, kimi əməliyyatları yenidən təhlil etməyəcəyik >=. <=Bunun əvəzinə daha maraqlı bir nümunəyə baxaq:
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);
   }
}
Sizcə ekranda hansı nömrə görünəcək? Məntiqi cavab cavab olardı: 1 rəqəmi. Biz 0,0 rəqəmindən saymağa başlayırıq və ardıcıl olaraq ona 10 dəfə 0,1 əlavə edirik. Hər şey düzgün görünür, bir olmalıdır. Bu kodu işə salmağa çalışın və cavab sizi çox təəccübləndirəcək :) Konsol çıxışı:

0.9999999999999999
Bəs niyə belə sadə bir nümunədə səhv baş verdi? O_o Burada hətta beşinci sinif şagirdi də asanlıqla düzgün cavab verə bilərdi, lakin Java proqramı qeyri-dəqiq nəticə verdi. Burada "yanlış" sözü "yanlış" sözündən daha yaxşıdır. Biz hələ də 1-ə çox yaxın rəqəm əldə etdik və sadəcə bəzi təsadüfi qiymət deyil :) O, düzgün olandan sözün əsl mənasında bir millimetr ilə fərqlənir. Bəs niyə? Bəlkə də bu birdəfəlik səhvdir. Bəlkə kompüter qəzaya uğrayıb? Başqa bir misal yazmağa çalışaq.
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 çıxışı:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
Deməli, bu, açıq-aydın kompüter nasazlığı məsələsi deyil :) Nə baş verir? Bu kimi xətalar nömrələrin kompüterin yaddaşında ikili formada əks olunması ilə əlaqədardır. Fakt budur ki, ikili sistemdə 0.1 rəqəmini dəqiq təmsil etmək mümkün deyil . Yeri gəlmişkən, onluq sistemdə də oxşar problem var: kəsrləri düzgün təmsil etmək mümkün deyil (və ⅓ əvəzinə biz 0,33333333333333... alırıq, bu da tam düzgün nəticə deyil). Xırda görünə bilər: bu cür hesablamalarla fərq yüz mində bir hissə (0,00001) və ya daha az ola bilər. Bəs Çox Ciddi Proqramınızın bütün nəticəsi bu müqayisədən asılıdırsa necə?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
Biz açıq şəkildə iki rəqəmin bərabər olacağını gözləyirdik, lakin daxili yaddaş dizaynına görə raket buraxılışını ləğv etdik. Həqiqi ədədlər cihazı - 3Əgər belədirsə, biz iki üzən nöqtəli ədədi necə müqayisə edəcəyimizə qərar verməliyik ki, müqayisənin nəticəsi daha çox... ummm... proqnozlaşdırıla bilən olsun. Beləliklə, biz artıq həqiqi ədədləri müqayisə edərkən 1 nömrəli qaydanı öyrənmişik: əsl ədədləri müqayisə edərkən heç vaxt üzən nöqtəli ədədlərdən istifadə etməyin . == Yaxşı, məncə bu qədər pis nümunələr kifayətdir :) Gəlin yaxşı bir nümunəyə baxaq!
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");
   }
}
Burada biz mahiyyətcə eyni şeyi edirik, lakin rəqəmləri müqayisə etmək üsulumuzu dəyişirik. Xüsusi bir "həddi" nömrəmiz var - 0.0001, on mində biri. Fərqli ola bilər. Bu, müəyyən bir vəziyyətdə nə qədər dəqiq bir müqayisəyə ehtiyacınız olduğundan asılıdır. Böyük və ya kiçik edə bilərsiniz. Metoddan istifadə edərək Math.abs()ədədin modulunu əldə edirik. Modul işarədən asılı olmayaraq ədədin qiymətidir. Məsələn, -5 və 5 ədədləri eyni modula malik olacaq və 5-ə bərabər olacaq. Birincidən ikinci ədədi çıxarırıq və nəticədə işarədən asılı olmayaraq nəticə müəyyən etdiyimiz həddən azdırsa, onda saylarımız bərabərdir. Hər halda, onlar bizim “ərəfəsində” istifadə edərək müəyyən etdiyimiz dəqiqlik dərəcəsinə bərabərdirlər , yəni minimum on mində birinə bərabərdirlər. Bu müqayisə üsulu sizi hadisədə gördüyümüz gözlənilməz davranışdan xilas edəcək ==. Həqiqi ədədləri müqayisə etməyin başqa bir yaxşı yolu xüsusi sinifdən istifadə etməkdir BigDecimal. Bu sinif xüsusi olaraq kəsr hissəsi olan çox böyük ədədləri saxlamaq üçün yaradılmışdır. doubleToplama və -dən fərqli olaraq float, BigDecimalçıxma və digər riyazi əməliyyatlar operatorlardan ( +-, və s.) deyil, metodlardan istifadə etməklə yerinə yetirilir. Bizim vəziyyətimizdə belə görünəcək:
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");
   }
}
Nə cür konsol çıxışı əldə edəcəyik?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
Tam gözlədiyimiz nəticəni aldıq. Nömrələrimizin nə qədər dəqiq olduğuna və onlara neçə onluq yer uyğun olduğuna diqqət yetirin! floatİçərisində və hətta içində olduğundan daha çox double! Gələcək üçün dərsi xatırlayın BigDecimal, mütləq ehtiyacınız olacaq :) Phew! Mühazirə kifayət qədər uzun idi, amma siz bunu bacardınız: yaxşı! :) Növbəti dərsdə görüşənədək gələcək proqramçı!
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION