JavaRush /Blog Java /Random-MS /Peranti nombor nyata

Peranti nombor nyata

Diterbitkan dalam kumpulan
hello! Dalam kuliah hari ini kita akan bercakap tentang nombor di Jawa, dan khususnya tentang nombor nyata. Peranti nombor nyata - 1Jangan panik! :) Tidak akan ada kesukaran matematik dalam kuliah. Kami akan bercakap tentang nombor nyata secara eksklusif dari sudut pandangan "pengaturcara" kami. Jadi, apakah itu "nombor nyata"? Nombor nyata ialah nombor yang mempunyai bahagian pecahan (yang boleh menjadi sifar). Mereka boleh menjadi positif atau negatif. Berikut ialah beberapa contoh: 15 56.22 0.0 1242342343445246 -232336.11 Bagaimanakah nombor nyata berfungsi? Agak mudah: ia terdiri daripada bahagian integer, bahagian pecahan dan tanda. Untuk nombor positif tanda biasanya tidak ditunjukkan secara eksplisit, tetapi untuk nombor negatif ia ditunjukkan. Sebelum ini, kami meneliti secara terperinci apakah operasi pada nombor yang boleh dilakukan di Jawa. Antaranya ialah banyak operasi matematik standard - tambah, tolak, dsb. Terdapat juga beberapa operasi baharu untuk anda: contohnya, baki bahagian. Tetapi bagaimana sebenarnya kerja dengan nombor berfungsi di dalam komputer? Dalam bentuk apakah ia disimpan dalam ingatan?

Menyimpan nombor nyata dalam ingatan

Saya fikir ia tidak akan menjadi penemuan untuk anda bahawa nombor boleh menjadi besar dan kecil :) Mereka boleh dibandingkan antara satu sama lain. Sebagai contoh, nombor 100 adalah kurang daripada nombor 423324. Adakah ini menjejaskan operasi komputer dan program kami? Sebenarnya ya . Setiap nombor diwakili dalam Java dengan julat nilai tertentu :
taip Saiz memori (bit) Julat nilai
byte 8 bit -128 hingga 127
short 16 bit -32768 hingga 32767
char 16 bit integer tidak bertanda yang mewakili aksara UTF-16 (huruf dan nombor)
int 32 bit daripada -2147483648 hingga 2147483647
long 64 bit daripada -9223372036854775808 hingga 9223372036854775807
float 32 bit dari 2 -149 hingga (2-2 -23 )*2 127
double 64 bit dari 2 -1074 hingga (2-2 -52 )*2 1023
Hari ini kita akan bercakap tentang dua jenis terakhir - floatdan double. Kedua-duanya melakukan tugas yang sama - mewakili nombor pecahan. Mereka juga sering dipanggil " nombor titik terapung" . Ingat istilah ini untuk masa hadapan :) Contohnya, nombor 2.3333 atau 134.1212121212. Agak pelik. Lagipun, ternyata tidak ada perbezaan antara kedua-dua jenis ini, kerana mereka melakukan tugas yang sama? Tetapi ada perbezaan. Beri perhatian kepada lajur "saiz dalam ingatan" dalam jadual di atas. Semua nombor (dan bukan hanya nombor - semua maklumat secara umum) disimpan dalam memori komputer dalam bentuk bit. A bit ialah unit maklumat terkecil. Ia agak mudah. Mana-mana bit adalah sama dengan 0 atau 1. Dan perkataan “ bit ” itu sendiri berasal daripada bahasa Inggeris “ binary digit ” - nombor binari. Saya rasa anda mungkin pernah mendengar tentang kewujudan sistem nombor binari dalam matematik. Mana-mana nombor perpuluhan yang kita kenali boleh diwakili sebagai set satu dan sifar. Sebagai contoh, nombor 584.32 dalam binari akan kelihatan seperti ini: 100100100001010001111 . Setiap satu dan sifar dalam nombor ini adalah bit yang berasingan. Sekarang anda sepatutnya lebih jelas tentang perbezaan antara jenis data. Sebagai contoh, jika kita mencipta beberapa jenis float, kita hanya mempunyai 32 bit sahaja. Apabila mencipta nombor, floatini adalah jumlah ruang yang akan diperuntukkan untuknya dalam memori komputer. Jika kita ingin mencipta nombor 123456789.65656565656565, dalam binari ia akan kelihatan seperti ini: 11101011011110011010001010110101000000 . Ia terdiri daripada 38 satu dan sifar, iaitu, 38 bit diperlukan untuk menyimpannya dalam ingatan. floatNombor ini tidak akan "sesuai" dengan jenis ! Oleh itu, nombor 123456789 boleh diwakili sebagai jenis double. Sebanyak 64 bit diperuntukkan untuk menyimpannya: ini sesuai dengan kita! Sudah tentu, julat nilai juga akan sesuai. Untuk kemudahan, anda boleh menganggap nombor sebagai kotak kecil dengan sel. Jika terdapat sel yang mencukupi untuk menyimpan setiap bit, maka jenis data dipilih dengan betul :) Peranti nombor nyata - 2Sudah tentu, jumlah memori yang diperuntukkan yang berbeza juga mempengaruhi nombor itu sendiri. Sila ambil perhatian bahawa jenis floatmempunyai doublejulat nilai yang berbeza. Apakah maksud ini dalam amalan? Nombor doubleboleh menyatakan ketepatan yang lebih besar daripada nombor float. Nombor titik terapung 32-bit (di Jawa ini betul-betul jenisnya float) mempunyai ketepatan kira-kira 24 bit, iaitu kira-kira 7 tempat perpuluhan. Dan nombor 64-bit (di Java ini adalah jenisnya double) mempunyai ketepatan kira-kira 53 bit, iaitu kira-kira 16 tempat perpuluhan. Berikut ialah contoh yang menunjukkan perbezaan ini dengan baik:
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);
   }
}
Apa yang patut kita dapat di sini sebagai hasilnya? Nampaknya semuanya agak mudah. Kami mempunyai nombor 0.0, dan kami menambah 0.11111111111111111 kepadanya 7 kali berturut-turut. Hasilnya hendaklah 0.777777777777777. Tetapi kami mencipta nombor float. Saiznya terhad kepada 32 bit dan, seperti yang kami katakan sebelum ini, ia mampu memaparkan nombor sehingga kira-kira tempat perpuluhan ke-7. Oleh itu, pada akhirnya, hasil yang kami dapat dalam konsol akan berbeza daripada apa yang kami jangkakan:

0.7777778
Nombor itu nampaknya "diputuskan". Anda sudah tahu bagaimana data disimpan dalam ingatan - dalam bentuk bit, jadi ini tidak sepatutnya mengejutkan anda. Jelas mengapa ini berlaku: keputusan 0.7777777777777777 hanya tidak sesuai dengan 32 bit yang diperuntukkan kepada kami, jadi ia dipotong untuk dimuatkan ke dalam pembolehubah jenis float:) Kami boleh menukar jenis pembolehubah doubledalam contoh kami, dan kemudian yang terakhir keputusan tidak akan dipotong:
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
Sudah terdapat 16 tempat perpuluhan, hasilnya "sesuai" menjadi 64 bit. Ngomong-ngomong, mungkin anda perasan bahawa dalam kedua-dua kes hasilnya tidak sepenuhnya betul? Pengiraan dibuat dengan kesilapan kecil. Kita akan bercakap tentang sebab untuk ini di bawah :) Sekarang mari kita sebutkan beberapa perkataan tentang cara anda boleh membandingkan nombor antara satu sama lain.

Perbandingan nombor nyata

Kami sebahagiannya telah menyentuh isu ini dalam kuliah lepas, apabila kita bercakap tentang operasi perbandingan. Kami tidak akan menganalisis semula operasi seperti >, <, >=. <=Mari kita lihat contoh yang lebih menarik sebaliknya:
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);
   }
}
Apakah nombor yang anda fikir akan dipaparkan pada skrin? Jawapan logik ialah jawapan: nombor 1. Kami mula mengira dari nombor 0.0 dan berturut-turut menambah 0.1 kepadanya sepuluh kali berturut-turut. Semuanya nampak betul, sepatutnya satu. Cuba jalankan kod ini, dan jawapannya akan sangat mengejutkan anda :) Output konsol:

0.9999999999999999
Tetapi mengapa ralat berlaku dalam contoh yang begitu mudah? O_o Di sini walaupun pelajar darjah lima boleh menjawab dengan mudah, tetapi program Java menghasilkan keputusan yang tidak tepat. "Tidak tepat" adalah perkataan yang lebih baik di sini daripada "tidak betul." Kami masih mendapat nombor yang sangat hampir dengan satu, dan bukan hanya beberapa nilai rawak :) Ia berbeza daripada yang betul secara literal dengan milimeter. Tapi kenapa? Mungkin ini hanya kesilapan sekali sahaja. Mungkin komputer rosak? Cuba kita tulis contoh lain.
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!");
   }
}
Output konsol:

f1 = 1.0999999999999999
f2 = 1.1
f1 и f2 не равны!
Jadi, ini jelas bukan masalah gangguan komputer :) Apa yang sedang berlaku? Jenis ralat ini berkaitan dengan cara nombor diwakili dalam bentuk binari dalam ingatan komputer. Hakikatnya ialah dalam sistem binari adalah mustahil untuk mewakili nombor 0.1 dengan tepat . Dengan cara ini, sistem perpuluhan juga mempunyai masalah yang sama: adalah mustahil untuk mewakili pecahan dengan betul (dan bukannya ⅓ kita mendapat 0.33333333333333..., yang juga bukan hasil yang betul). Ia kelihatan seperti remeh: dengan pengiraan sedemikian, perbezaannya boleh menjadi seratus ribu bahagian (0.00001) atau lebih kurang. Tetapi bagaimana jika keseluruhan hasil Program Sangat Serius anda bergantung pada perbandingan ini?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
Kami jelas menjangkakan kedua-dua nombor itu adalah sama, tetapi disebabkan reka bentuk memori dalaman, kami membatalkan pelancaran roket. Peranti nombor nyata - 3Jika ya, kita perlu memutuskan bagaimana untuk membandingkan dua nombor titik terapung supaya hasil perbandingan itu lebih... emm... boleh diramal. Jadi, kita telah mempelajari peraturan No. 1 apabila membandingkan nombor nyata: jangan sekali-kali menggunakan ==nombor titik terapung apabila membandingkan nombor nyata. Ok, saya rasa itu sudah cukup contoh buruk :) Mari kita lihat contoh yang baik!
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");
   }
}
Di sini kita pada dasarnya melakukan perkara yang sama, tetapi mengubah cara kita membandingkan nombor. Kami mempunyai nombor "ambang" khas - 0.0001, satu persepuluh ribu. Ia mungkin berbeza. Ia bergantung pada seberapa tepat perbandingan yang anda perlukan dalam kes tertentu. Anda boleh menjadikannya lebih besar atau lebih kecil. Menggunakan kaedah, Math.abs()kita memperoleh modulus nombor. Modulus ialah nilai nombor tanpa mengira tanda. Sebagai contoh, nombor -5 dan 5 akan mempunyai modulus yang sama dan bersamaan dengan 5. Kami menolak nombor kedua daripada yang pertama, dan jika hasil yang terhasil, tanpa mengira tanda, adalah kurang daripada ambang yang kami tetapkan, maka bilangan kita sama. Walau apa pun, ia adalah sama dengan tahap ketepatan yang kami tetapkan menggunakan "nombor ambang" kami , iaitu, sekurang-kurangnya ia adalah sama hingga satu persepuluh ribu. Kaedah perbandingan ini akan menyelamatkan anda daripada tingkah laku yang tidak dijangka yang kami lihat dalam kes ==. Satu lagi cara yang baik untuk membandingkan nombor nyata ialah menggunakan kelas khas BigDecimal. Kelas ini dicipta khusus untuk menyimpan nombor yang sangat besar dengan bahagian pecahan. Tidak seperti doubledan float, apabila menggunakan BigDecimalpenambahan, penolakan dan operasi matematik lain dilakukan bukan menggunakan operator ( +-, dsb.), tetapi menggunakan kaedah. Inilah yang akan kelihatan dalam kes kami:
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");
   }
}
Apakah jenis keluaran konsol yang akan kami perolehi?

f1 = 1.1000000000000000610622663543836097232997417449951171875
f2 = 1.1000000000000000610622663543836097232997417449951171875
f1 и f2 равны
Kami mendapat hasil yang kami jangkakan. Dan perhatikan seberapa tepat nombor kami ternyata, dan berapa banyak tempat perpuluhan yang sesuai dengannya! Lebih banyak daripada dalam floatdan juga dalam double! Ingat kelas BigDecimaluntuk masa hadapan, anda pasti memerlukannya :) Fuh! Kuliah itu agak panjang, tetapi anda melakukannya: syabas! :) Jumpa anda dalam pelajaran seterusnya, pengaturcara masa depan!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION