JavaRush /Java Blog /Random-TW /實數裝置

實數裝置

在 Random-TW 群組發布
你好!在今天的講座中,我們將討論 Java 中的數字,特別是實數。 實數裝置 - 1不要恐慌!:) 講座中不會有數學困難。我們將完全從「程式設計師」的角度來討論實數。那麼,什麼是「實數」呢? 實數是具有小數部分(可以為零)的數字。它們可以是積極的,也可以是消極的。以下是一些範例: 15 56.22 0.0 1242342343445246 -232336.11 實數如何運作?很簡單:它由整數部分、小數部分和符號組成。對於正數,通常不明確指示符號,但對於負數,則指示符號。之前,我們詳細研究了Java 中可以對數字執行哪些操作。其中有許多標準的數學運算 - 加法、減法等。還有一些新的運算可供您使用:例如除法的餘數。但計算機內部的數位處理到底是如何進行的呢?它們以什麼形式儲存在記憶體中?

在記憶體中儲存實數

我認為數字可以大也可以小,這對你來說不會是一個發現:)它們可以相互比較。例如數字100小於數字423324,這會影響電腦和我們程式的運作嗎?其實,每個數字在 Java 中都由特定範圍的值表示:
類型 記憶體大小(位元) 數值範圍
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
今天我們將討論最後兩種類型 -floatdouble。兩者執行相同的任務 - 表示小數。它們也常被稱為“浮點數”。請記住這個術語以備將來使用:) 例如,數字 2.3333 或 134.1212121212。很奇怪。畢竟,事實證明這兩種類型之間沒有區別,因為它們執行相同的任務?但有一個差別。請注意上表中的「記憶體大小」欄位。所有數字(不僅僅是數字 - 一般而言所有資訊)都以位元的形式儲存在電腦記憶體中。位是最小的資訊單位。這很簡單。任何位都等於 0 或 1。“”一詞本身來自英語“二進制數字”——二進制數。我想您可能聽說過數學中二進制數字系統的存在。我們熟悉的任何十進制數都可以表示為一組 1 和 0。例如,二進位數 584.32 如下所示:100100100001010001111。該數字中的每個 1 和 0 都是一個單獨的位元。現在您應該更清楚資料類型之間的差異了。例如,如果我們建立一個數字 type float,我們只有 32 位元可供使用。在創建數字時,float這正是在電腦記憶體中為其分配的空間量。如果我們要建立數字 123456789.65656565656565,二進位形式將如下所示: 11101011011110011010001010110101000000。它由 38 個 1 和 0 組成,即需要 38 位元來儲存在記憶體中。float這個數字根本不“適合”該類型!因此,數字 123456789 可以表示為 type double。分配多達 64 位元來儲存它:這適合我們!當然,取值範圍也會合適。為了方便起見,您可以將數字視為帶有單元格的小盒子。如果有足夠的單元來儲存每位,則資料類型選擇正確:) 實數裝置 - 2當然,分配的記憶體量不同也會影響數字本身。請注意,類型float具有double不同的值範圍。這在實踐中意味著什麼?數字double可以表達比數字更高的精度float。32 位元浮點數(在 Java 中正是 類型float)的精確度約為 24 位,即大約 7 位小數。64 位數字(在 Java 中是 類型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,我們連續向它增加 0.1111111111111111 7 次。結果應為 0.7777777777777777。但我們創造了一個數字float。它的大小限制為 32 位,正如我們之前所說,它能夠顯示最多小數點後第七位的數字。因此,最終我們在控制台得到的結果會和我們預想的不一樣:

0.7777778
這個號碼似乎被“切斷”了。您已經知道資料如何以位元的形式儲存在記憶體中,所以這應該不會令您感到驚訝。發生這種情況的原因很清楚:結果 0.7777777777777777 根本不適合分配給我們的 32 位,因此它被截斷以適合類型變數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 這裡即使是五年級學生也能輕鬆回答正確,但 Java 程式產生的結果不準確。「不準確」在這裡比「不正確」好。我們仍然得到一個非常接近 1 的數字,而不僅僅是一些隨機值:)它與正確的數字相差一毫米。但為什麼?也許這只是一次性的錯誤。也許電腦當機了?讓我們試著寫另一個例子。
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.33333333333333...,這也不是完全正確的結果)。這似乎是一件小事:透過這樣的計算,差異可能是萬分之一 (0.00001) 甚至更小。但是,如果您的 Very Serious 計劃的整個結果取決於這種比較怎麼辦?
if (f1 == f2)
   System.out.println("Rocket flies into space");
else
   System.out.println("The launch is canceled, everyone goes home");
我們顯然期望這兩個數字相等,但由於記憶體設計,我們取消了火箭發射。 實數裝置 - 3如果是這樣,我們需要決定如何比較兩個浮點數,以便比較結果更......嗯......可預測。因此,我們已經學會了比較實數時的第一條規則:比較實數時切勿使用==浮點數。 好吧,我認為壞例子已經夠多了:)讓我們來看一個好例子!
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與and不同的是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比 in甚至 in多得多doubleBigDecimal記住將來的課程,你一定會需要它:)唷!講座很長,但你做到了:幹得好!:) 下一課見,未來的程式設計師!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION