'java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪'

Java 電腦 斯蒂芬朱cPlusPlus 2019-07-19
"

前言:在工作中,談到有小數點的加減乘除都會想到用BigDecimal來解決,但是有很多人對於double或者float為啥會丟失精度一臉茫然。還有BigDecimal是怎麼解決的?話不多說,我們開始。

1.浮點數是啥?

浮點數是計算機用來表示小數的一種數據類型,採用科學計數法。在java中,double是雙精度,64位,浮點數,默認是0.0d。float是單精度,32位.浮點數,默認是0.0f;

在內存中存儲

"

前言:在工作中,談到有小數點的加減乘除都會想到用BigDecimal來解決,但是有很多人對於double或者float為啥會丟失精度一臉茫然。還有BigDecimal是怎麼解決的?話不多說,我們開始。

1.浮點數是啥?

浮點數是計算機用來表示小數的一種數據類型,採用科學計數法。在java中,double是雙精度,64位,浮點數,默認是0.0d。float是單精度,32位.浮點數,默認是0.0f;

在內存中存儲

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

float 符號位(1bit) 指數(8 bit) 尾數(23 bit)

double 符號位(1bit) 指數(11 bit) 尾數(52 bit)

float在內存中佔8位,由於階碼實際存儲的是指數的移碼,假設指數的真值是e,階碼為E,則有E=e+(2^n-1 -1)。其中 2^n-1 -1是IEEE754標準規定的指數偏移量,根據這個公式我們可以得到 2^8 -1=127。於是,float的指數範圍為-128 +127,而double的指數範圍為-1024 +1023。其中負指數決定了浮點數所能表達的絕對值最小的非零數;而正指數決定了浮點數所能表達的絕對值最大的數,也即決定了浮點數的取值範圍。

loat的範圍為-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;

double的範圍為-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

2.走進失真之科學計數法

我們先說說科學計數法,科學計數法是一種簡化計數的方法,用來近似表示一個極大或極小且位數較多的數,對於位數較小的數值,科學計數法沒有什麼優勢,但對於位數較多的數值其計數方法的優勢就非常明顯了。例如:光的速速是300000000米/秒,全世界人口數大約是6100000000。類似光的速度和世界人口數這樣大數值的數,讀、寫都很不方便,所以光的速度可以寫成3*10^8,全世界人口數可以寫成6.1*10^9。所以計算器用科學計數法表示光速是3E8,世界人口數大約是6.1E9。

我們小時候玩計算器喜歡瘋狂的累加或者累減,到最後計算器就會顯示下圖。這個就是科學計數法顯示的結果

"

前言:在工作中,談到有小數點的加減乘除都會想到用BigDecimal來解決,但是有很多人對於double或者float為啥會丟失精度一臉茫然。還有BigDecimal是怎麼解決的?話不多說,我們開始。

1.浮點數是啥?

浮點數是計算機用來表示小數的一種數據類型,採用科學計數法。在java中,double是雙精度,64位,浮點數,默認是0.0d。float是單精度,32位.浮點數,默認是0.0f;

在內存中存儲

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

float 符號位(1bit) 指數(8 bit) 尾數(23 bit)

double 符號位(1bit) 指數(11 bit) 尾數(52 bit)

float在內存中佔8位,由於階碼實際存儲的是指數的移碼,假設指數的真值是e,階碼為E,則有E=e+(2^n-1 -1)。其中 2^n-1 -1是IEEE754標準規定的指數偏移量,根據這個公式我們可以得到 2^8 -1=127。於是,float的指數範圍為-128 +127,而double的指數範圍為-1024 +1023。其中負指數決定了浮點數所能表達的絕對值最小的非零數;而正指數決定了浮點數所能表達的絕對值最大的數,也即決定了浮點數的取值範圍。

loat的範圍為-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;

double的範圍為-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

2.走進失真之科學計數法

我們先說說科學計數法,科學計數法是一種簡化計數的方法,用來近似表示一個極大或極小且位數較多的數,對於位數較小的數值,科學計數法沒有什麼優勢,但對於位數較多的數值其計數方法的優勢就非常明顯了。例如:光的速速是300000000米/秒,全世界人口數大約是6100000000。類似光的速度和世界人口數這樣大數值的數,讀、寫都很不方便,所以光的速度可以寫成3*10^8,全世界人口數可以寫成6.1*10^9。所以計算器用科學計數法表示光速是3E8,世界人口數大約是6.1E9。

我們小時候玩計算器喜歡瘋狂的累加或者累減,到最後計算器就會顯示下圖。這個就是科學計數法顯示的結果

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

那圖中真實的值是 -4.86*10^11=-486000000000。十進制科學計數法要求有效數字的整數部分必須在【1,9】區間內。

3.走進失真之精度

計算機在處理數據都涉及到數據的轉換和各種複雜運算,比如,不同單位換算,不同進制(如二進制十進制)換算等,很多除法運算不能除盡,比如10÷3=3.3333.....無窮無盡,而精度是有限的,3.3333333x3並不等於10,經過複雜的處理後得到的十進制數據並不精確,精度越高越精確。float和double的精度是由尾數的位數來決定的,其整數部分始終是一個隱含著的“1”,由於它是不變的,故不能對精度造成影響。float:2^23 = 8388608,一共七位,由於最左為1的一位省略了,這意味著最多能表示8位數: 28388608 = 16777216 。有8位有效數字,但絕對能保證的為7位,也即float的精度為7~8位有效數字;double:2^52 = 4503599627370496,一共16位,同理,double的精度為16~17位

"

前言:在工作中,談到有小數點的加減乘除都會想到用BigDecimal來解決,但是有很多人對於double或者float為啥會丟失精度一臉茫然。還有BigDecimal是怎麼解決的?話不多說,我們開始。

1.浮點數是啥?

浮點數是計算機用來表示小數的一種數據類型,採用科學計數法。在java中,double是雙精度,64位,浮點數,默認是0.0d。float是單精度,32位.浮點數,默認是0.0f;

在內存中存儲

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

float 符號位(1bit) 指數(8 bit) 尾數(23 bit)

double 符號位(1bit) 指數(11 bit) 尾數(52 bit)

float在內存中佔8位,由於階碼實際存儲的是指數的移碼,假設指數的真值是e,階碼為E,則有E=e+(2^n-1 -1)。其中 2^n-1 -1是IEEE754標準規定的指數偏移量,根據這個公式我們可以得到 2^8 -1=127。於是,float的指數範圍為-128 +127,而double的指數範圍為-1024 +1023。其中負指數決定了浮點數所能表達的絕對值最小的非零數;而正指數決定了浮點數所能表達的絕對值最大的數,也即決定了浮點數的取值範圍。

loat的範圍為-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;

double的範圍為-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

2.走進失真之科學計數法

我們先說說科學計數法,科學計數法是一種簡化計數的方法,用來近似表示一個極大或極小且位數較多的數,對於位數較小的數值,科學計數法沒有什麼優勢,但對於位數較多的數值其計數方法的優勢就非常明顯了。例如:光的速速是300000000米/秒,全世界人口數大約是6100000000。類似光的速度和世界人口數這樣大數值的數,讀、寫都很不方便,所以光的速度可以寫成3*10^8,全世界人口數可以寫成6.1*10^9。所以計算器用科學計數法表示光速是3E8,世界人口數大約是6.1E9。

我們小時候玩計算器喜歡瘋狂的累加或者累減,到最後計算器就會顯示下圖。這個就是科學計數法顯示的結果

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

那圖中真實的值是 -4.86*10^11=-486000000000。十進制科學計數法要求有效數字的整數部分必須在【1,9】區間內。

3.走進失真之精度

計算機在處理數據都涉及到數據的轉換和各種複雜運算,比如,不同單位換算,不同進制(如二進制十進制)換算等,很多除法運算不能除盡,比如10÷3=3.3333.....無窮無盡,而精度是有限的,3.3333333x3並不等於10,經過複雜的處理後得到的十進制數據並不精確,精度越高越精確。float和double的精度是由尾數的位數來決定的,其整數部分始終是一個隱含著的“1”,由於它是不變的,故不能對精度造成影響。float:2^23 = 8388608,一共七位,由於最左為1的一位省略了,這意味著最多能表示8位數: 28388608 = 16777216 。有8位有效數字,但絕對能保證的為7位,也即float的精度為7~8位有效數字;double:2^52 = 4503599627370496,一共16位,同理,double的精度為16~17位

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

當到達一定值自動開始使用科學計數法,並保留相關精度的有效數字,所以結果是個近似數,並且指數為整數。在十進制中小數有些是無法完整用二進制表示的。所以只能用有限位來表示,從而在存儲時可能就會有誤差。對於十進制的小數轉換成二進制採用乘2取整法進行計算,取掉整數部分後,剩下的小數繼續乘以2,直到小數部分全為0。

如遇到

"

前言:在工作中,談到有小數點的加減乘除都會想到用BigDecimal來解決,但是有很多人對於double或者float為啥會丟失精度一臉茫然。還有BigDecimal是怎麼解決的?話不多說,我們開始。

1.浮點數是啥?

浮點數是計算機用來表示小數的一種數據類型,採用科學計數法。在java中,double是雙精度,64位,浮點數,默認是0.0d。float是單精度,32位.浮點數,默認是0.0f;

在內存中存儲

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

float 符號位(1bit) 指數(8 bit) 尾數(23 bit)

double 符號位(1bit) 指數(11 bit) 尾數(52 bit)

float在內存中佔8位,由於階碼實際存儲的是指數的移碼,假設指數的真值是e,階碼為E,則有E=e+(2^n-1 -1)。其中 2^n-1 -1是IEEE754標準規定的指數偏移量,根據這個公式我們可以得到 2^8 -1=127。於是,float的指數範圍為-128 +127,而double的指數範圍為-1024 +1023。其中負指數決定了浮點數所能表達的絕對值最小的非零數;而正指數決定了浮點數所能表達的絕對值最大的數,也即決定了浮點數的取值範圍。

loat的範圍為-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;

double的範圍為-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

2.走進失真之科學計數法

我們先說說科學計數法,科學計數法是一種簡化計數的方法,用來近似表示一個極大或極小且位數較多的數,對於位數較小的數值,科學計數法沒有什麼優勢,但對於位數較多的數值其計數方法的優勢就非常明顯了。例如:光的速速是300000000米/秒,全世界人口數大約是6100000000。類似光的速度和世界人口數這樣大數值的數,讀、寫都很不方便,所以光的速度可以寫成3*10^8,全世界人口數可以寫成6.1*10^9。所以計算器用科學計數法表示光速是3E8,世界人口數大約是6.1E9。

我們小時候玩計算器喜歡瘋狂的累加或者累減,到最後計算器就會顯示下圖。這個就是科學計數法顯示的結果

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

那圖中真實的值是 -4.86*10^11=-486000000000。十進制科學計數法要求有效數字的整數部分必須在【1,9】區間內。

3.走進失真之精度

計算機在處理數據都涉及到數據的轉換和各種複雜運算,比如,不同單位換算,不同進制(如二進制十進制)換算等,很多除法運算不能除盡,比如10÷3=3.3333.....無窮無盡,而精度是有限的,3.3333333x3並不等於10,經過複雜的處理後得到的十進制數據並不精確,精度越高越精確。float和double的精度是由尾數的位數來決定的,其整數部分始終是一個隱含著的“1”,由於它是不變的,故不能對精度造成影響。float:2^23 = 8388608,一共七位,由於最左為1的一位省略了,這意味著最多能表示8位數: 28388608 = 16777216 。有8位有效數字,但絕對能保證的為7位,也即float的精度為7~8位有效數字;double:2^52 = 4503599627370496,一共16位,同理,double的精度為16~17位

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

當到達一定值自動開始使用科學計數法,並保留相關精度的有效數字,所以結果是個近似數,並且指數為整數。在十進制中小數有些是無法完整用二進制表示的。所以只能用有限位來表示,從而在存儲時可能就會有誤差。對於十進制的小數轉換成二進制採用乘2取整法進行計算,取掉整數部分後,剩下的小數繼續乘以2,直到小數部分全為0。

如遇到

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

輸出是 0.19999999999999998
double類型 0.3-0.1的情況。需要將0.3轉成二進制在運算
0.3 * 2 = 0.6 => .0 (.6)取0剩0.6
0.6 * 2 = 1.2 => .01 (.2)取1剩0.2
0.2 * 2 = 0.4 => .010 (.4)取0剩0.4
0.4 * 2 = 0.8 => .0100 (.8) 取0剩0.8
0.8 * 2 = 1.6 => .01001 (.6)取1剩0.6
.............

3.總結

從上面看,很清楚為什麼浮點數有精度問題。簡單地說,float和double類型主要是為科學計算和工程計算而設計的。它們執行二進制浮點運算,這些運算經過精心設計,能夠在廣泛的數值範圍內提供更精確的快速近似和計算而精心設計的。但是,它們不能提供完全準確的結果,因此不能用於需要計算精確結果的場景中。當浮點數達到一定的大數時自動使用科學計數法。這樣的表示只是近似真實數而不等於真實數。當十進制小數轉換為二進制時,也會出現無限循環或超出浮點數尾部的長度。

4.那我們怎麼用BigDecimal來解決?

大家看下面的兩個輸出

"

前言:在工作中,談到有小數點的加減乘除都會想到用BigDecimal來解決,但是有很多人對於double或者float為啥會丟失精度一臉茫然。還有BigDecimal是怎麼解決的?話不多說,我們開始。

1.浮點數是啥?

浮點數是計算機用來表示小數的一種數據類型,採用科學計數法。在java中,double是雙精度,64位,浮點數,默認是0.0d。float是單精度,32位.浮點數,默認是0.0f;

在內存中存儲

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

float 符號位(1bit) 指數(8 bit) 尾數(23 bit)

double 符號位(1bit) 指數(11 bit) 尾數(52 bit)

float在內存中佔8位,由於階碼實際存儲的是指數的移碼,假設指數的真值是e,階碼為E,則有E=e+(2^n-1 -1)。其中 2^n-1 -1是IEEE754標準規定的指數偏移量,根據這個公式我們可以得到 2^8 -1=127。於是,float的指數範圍為-128 +127,而double的指數範圍為-1024 +1023。其中負指數決定了浮點數所能表達的絕對值最小的非零數;而正指數決定了浮點數所能表達的絕對值最大的數,也即決定了浮點數的取值範圍。

loat的範圍為-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;

double的範圍為-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

2.走進失真之科學計數法

我們先說說科學計數法,科學計數法是一種簡化計數的方法,用來近似表示一個極大或極小且位數較多的數,對於位數較小的數值,科學計數法沒有什麼優勢,但對於位數較多的數值其計數方法的優勢就非常明顯了。例如:光的速速是300000000米/秒,全世界人口數大約是6100000000。類似光的速度和世界人口數這樣大數值的數,讀、寫都很不方便,所以光的速度可以寫成3*10^8,全世界人口數可以寫成6.1*10^9。所以計算器用科學計數法表示光速是3E8,世界人口數大約是6.1E9。

我們小時候玩計算器喜歡瘋狂的累加或者累減,到最後計算器就會顯示下圖。這個就是科學計數法顯示的結果

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

那圖中真實的值是 -4.86*10^11=-486000000000。十進制科學計數法要求有效數字的整數部分必須在【1,9】區間內。

3.走進失真之精度

計算機在處理數據都涉及到數據的轉換和各種複雜運算,比如,不同單位換算,不同進制(如二進制十進制)換算等,很多除法運算不能除盡,比如10÷3=3.3333.....無窮無盡,而精度是有限的,3.3333333x3並不等於10,經過複雜的處理後得到的十進制數據並不精確,精度越高越精確。float和double的精度是由尾數的位數來決定的,其整數部分始終是一個隱含著的“1”,由於它是不變的,故不能對精度造成影響。float:2^23 = 8388608,一共七位,由於最左為1的一位省略了,這意味著最多能表示8位數: 28388608 = 16777216 。有8位有效數字,但絕對能保證的為7位,也即float的精度為7~8位有效數字;double:2^52 = 4503599627370496,一共16位,同理,double的精度為16~17位

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

當到達一定值自動開始使用科學計數法,並保留相關精度的有效數字,所以結果是個近似數,並且指數為整數。在十進制中小數有些是無法完整用二進制表示的。所以只能用有限位來表示,從而在存儲時可能就會有誤差。對於十進制的小數轉換成二進制採用乘2取整法進行計算,取掉整數部分後,剩下的小數繼續乘以2,直到小數部分全為0。

如遇到

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

輸出是 0.19999999999999998
double類型 0.3-0.1的情況。需要將0.3轉成二進制在運算
0.3 * 2 = 0.6 => .0 (.6)取0剩0.6
0.6 * 2 = 1.2 => .01 (.2)取1剩0.2
0.2 * 2 = 0.4 => .010 (.4)取0剩0.4
0.4 * 2 = 0.8 => .0100 (.8) 取0剩0.8
0.8 * 2 = 1.6 => .01001 (.6)取1剩0.6
.............

3.總結

從上面看,很清楚為什麼浮點數有精度問題。簡單地說,float和double類型主要是為科學計算和工程計算而設計的。它們執行二進制浮點運算,這些運算經過精心設計,能夠在廣泛的數值範圍內提供更精確的快速近似和計算而精心設計的。但是,它們不能提供完全準確的結果,因此不能用於需要計算精確結果的場景中。當浮點數達到一定的大數時自動使用科學計數法。這樣的表示只是近似真實數而不等於真實數。當十進制小數轉換為二進制時,也會出現無限循環或超出浮點數尾部的長度。

4.那我們怎麼用BigDecimal來解決?

大家看下面的兩個輸出

java面試官:Double為什麼會丟失精度?解決方法?答出給1萬月薪

輸出結果:

0.299999999999999988897769753748434595763683319091796875

0.3

圖上阿里的代碼約束插件在圖表上已經標記了警告,所以讓我使用String字符串參數的構造方法創建BigDecimal。由於double不能精確表示為0.3(任何有限長度的二進制),因此用double構造函數傳遞的值不完全等於0.3。使用bigdecimal時,必須使用String字符串參數構造方法來創建它。在這一點上,有沒有好奇的疑問。BigDecimal原理是什麼?為什麼它就沒事?原理很簡單。BigDecimal是不可變的,可以用來表示任意精度的帶符號十進制數。double的問題是從小數點轉換到二進制丟失精度,二進制丟失精度。BigDecimal在處理的時候把十進制小數擴大N倍讓它在整數上進行計算,並保留相應的精度信息。至於BigDecimal是怎麼保存的可以翻閱一下源代碼。


5.總結

 (1)商業計算使用BigDecimal。
(2)儘量使用參數類型為String的構造函數。
(3) BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,所以在做加減乘除運算時千萬要保存操作後的值。
(4)我們往往容易忽略JDK底層的一些實現細節,導致出現錯誤,需要多加註意。
"

相關推薦

推薦中...