切換語言為:簡體
浮點數為什麼會丟失精度?BigDecimal為什麼不會?

浮點數為什麼會丟失精度?BigDecimal為什麼不會?

  • 爱糖宝
  • 2024-11-17
  • 2024
  • 0
  • 0

前言

學java的肯定都知道,要保證小數運算精度不丟失我們得用BigDecimal物件。這篇文章就分析一下為什麼用浮點數會造成精度丟失?BigDecimal是怎麼解決精度丟失問題的?下面我們一起看看吧!

浮點數的表示

浮點數在計算機中通常採用 IEEE 754 標準進行表示。這個標準將數值分為三個部分:符號位、指數部分和尾數部分。由於尾數的位數有限,某些小數(尤其是十進制小數)無法精確地用二進制表示。

精度丟失案例

public class FloatPrecision {
    public static void main(String[] args) {
        double a = 0.1;
        double b = 0.2;
        double c = a + b;
        System.out.println(c); // 輸出 0.30000000000000004
    }
}

在這個例子中,儘管我們期望 c 的值為 0.3,但實際上它被計算為 0.30000000000000004。這種現象在浮點數運算中非常常見,尤其是在涉及多個浮點數的加減乘除時,誤差可能會逐漸累積。

為什麼浮點數不能精確表示小數呢?

瞭解了10進位制小數轉二進制過程,就知道為啥呢?

十進制小數轉換成二進制小數採用"乘2取整,順序排列法:
● 用2乘十進制小數,可以得到積
● 將積的整數部分取出,再用2乘餘下的小數部分,又得到一個積
● 再將積的整數部分取出,如此進行,直到積中的小數部分為零,此時0或1為二進制的最後一位。或者達到所要求的精度為止。

浮點數為什麼會丟失精度?BigDecimal為什麼不會? 

0.00011001100110011...(迴圈)

BigDecimal 的解決方案

透過BigDecimal("0.1") 的String 構造不會造成精度丟失,避免建構函式用浮點數表示,上面已經說過了浮點數本身存在精度丟失的問題。BigDecimal是怎麼解決精度丟失的呢?

BigDecimal是透過一個"無標度值"和一個"標度"來表示一個數的。

關鍵得三個引數:

浮點數為什麼會丟失精度?BigDecimal為什麼不會?

無標度值(Unscaled Value):這是一個整數,表示BigDecimal的實際數值。
標度(Scale):這是一個整數,表示小數點後的位數。
BigDecimal的實際數值計算公式為:unscaledValue × 10^(-scale)。

假設有一個BigDecimal表示的數值是123.45,那麼無標度值(Unscaled Value)是12345。標度(Scale)是2。因為123.45 = 12345 × 10^(-2)。

當標度為正數時,它表示小數點後的位數。例如,在數字123.45中,他的無標度值為12345,標度是2。
當標度為零時,BigDecimal表示一個整數。
當標度為負數時,它表示小數點向左移動的位數,相當於將數字乘以 10 的絕對值的次方。
例如,一個數值為1234500,那麼他可以用value是12345,scale為-2來表示,因為1234500 * 10^(-2) = 12345。
(當需要處理非常大的整數時,可以使用負數的標度來指定小數點左側的位數。這在需要保持整數的精度而又不想丟失尾部零位時很有用。)

不能用BigDecimal的equals方法做等值

直接上原始碼吧:

public boolean equals(Object x) {
    if (!(x instanceof BigDecimal)) {
        return false;
    } else {
        BigDecimal xDec = (BigDecimal)x;
        if (x == this) {
            return true;
            //比較標度
        } else if (this.scale != xDec.scale) {
            return false;
        } else {
            long s = this.intCompact;
            long xs = xDec.intCompact;
            if (s != -9223372036854775808L) {
                if (xs == -9223372036854775808L) {
                    xs = compactValFor(xDec.intVal);
                }

                return xs == s;
            } else if (xs != -9223372036854775808L) {
                return xs == compactValFor(this.intVal);
            } else {
                return this.inflated().equals(xDec.inflated());
            }
        }
    }
}

equals方法會比較標度,所以比較大小的話用 compareTo()比較吧

import java.math.BigDecimal;

public class BigDecimalComparison {
    public static void main(String[] args) {
        BigDecimal num1 = new BigDecimal("1.0");
        BigDecimal num2 = new BigDecimal("1.00");
        System.out.println("equals: " + num1.equlse(num2));       // 輸出false
        System.out.println("compareTo: " + num1.compareTo(num2)); // 輸出: 0
    }
}

小結

因為某些十進制的小數在二進制中是一個無限迴圈的小數,所有用浮點數存在進度丟失問題。 BigDecimal採用標度的形式解決了精度丟失問題。同時在使用BigDecimal的時候,使用String的構造器。 最後在使用BigDecimal比較大小的時候不要使用equals,請用compareTo


0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.