1. 程式人生 > >關於浮點數與精確小數計算的理解

關於浮點數與精確小數計算的理解

下面這篇文章探討的是關於浮點數與精確小數計算的理解。

小數在大家的生活中太常見了,這玩意小學就教,計算機程式裡也經常用到,所以它可能不太被人注意。

但現實是,如果你不瞭解小數在計算機的世界裡是怎麼玩的,你就很可能在程式中因錯誤使用小數而犯錯。

本文不深入剖析小數在計算機中的表示形式(IEEE 754 規範),意在講清楚計算機中使用小數會出現什麼問題,以及解決方案。

浮點數帶來的問題

假如你在面試,面試官看到你有和金融,銀行相關的專案的經歷,可能會問你記錄存款和消費金額等與錢有關的資料時使用什麼資料型別?

如果你能答上來BigDecimal等小數工具類的話,他可能會繼續問你:為什麼放著現成的float和double不用,要使用小數工具類呢?

這裡就涉及到浮點數在計算機系統中儲存的問題了。

我們可以通過自己熟悉的語言快速地重新浮點數計算不精確的現象,本文以Java為例進行展示:

public class Main {
    public static void main(String[] args){
        System.out.println(0.01 + 0.09);
        System.out.println(1 - 0.32);
        System.out.println(1.015 * 100);
    }
}

//輸出結果:
0.09999999999999999
0.6799999999999999
101.49999999999999

那麼究竟為什麼會出現這樣的不精確現象呢?

眾所周知計算機的世界是一個二進位制的世界,計算機系統內部的一切資料終究會落實到0和1這兩個數字上。但我們人類世界中基本都是使用十進位制,要想把人類的問題交給計算機解決,就必須有一個把十進位制數轉換為二進位制數的過程,浮點數的問題就出在這個轉換的過程中。

想要把一個十進位制的整數轉換為二進位制,簡單!數學上只要對目標整數不斷除以二,直到除盡或餘數為1即可,這樣的轉換是精確的。其背後的原理是所有的整數都能通過2^n之和加上正負號來表示(n取所有正整數和0)

但是想要把一個十進位制的小數轉換為二進位制,就不那麼簡單了。根本原因在於2^n之和加上正負號(n取所有負整數)不能表示所有的小數!

從數學上把一個小數從十進位制轉換為二進位制,你需要對小數不斷乘以二,直到得到1才算轉換完成。事實上能完成這一轉換的小數不多,大多數小數才乘以二的過程中都會進入一個迴圈的序列,導致無法精確地把一個小數從十進位制轉換為二進位制。

解決浮點數問題,精確計算小數

既然我們已經知道了計算機能準確計算整數,但不能準確計算小數。那我們可以這樣想:如果把小數轉換為整數,讓計算機計算整數,在得出精確的計算結果後再轉換為小數,這樣計算結果不就精確了嗎?

按照這個思路,人們封裝了精確計算小數的工具類,在Java中這個工具類叫做BigDecimal,我們可以通過一次Debug驗證一下是否真的是這麼幹的:

public class Main {
    public static void main(String[] args){
        BigDecimal bd1 = new BigDecimal("123.456");
        BigDecimal bd2 = new BigDecimal("654.321");
        BigDecimal result = bd1.add(bd2);
        System.out.println(result.toString());
    }
}

//輸出結果:
777.777

參考文章:

https://zhuanlan.zhihu.com/p/71796835
https://mp.weixin.qq.com/s/Cd4uRslnek8r_a6chjwnYQ
https://www.jianshu.com/p/610448083