初學Java只簡單聽說double,float是不能精確的表示一個數钻注,然后留下滿滿的疑問篮撑? why? 這個問題已經放了好久時間了逞壁,今天終于把這個問題整明白了!首先整理一下以下幾個困惑
1.浮點數(double? float)真的會不精確绍弟?
我們在使用的過程中并沒有誤差凹技础?
double a = 0.4;
double b = a*4;
System.out.println(b); // 輸出 1.6
// 不是說浮點數不精確嗎樟遣?那我們做一個相等判斷
System.out.println(a == 0.4); //輸出 true
// 我去而叼,哪有什么不精確!1葵陵!瞎BB
當我們放心大膽的用浮點數時
System.out.println (0.4 * 0.2);
// 輸出 0.08000000000000002
System.out.println(40000000.0f == 40000000.5f); // float類型字面量
// 輸出 true
System.out.println(4000000000000000.0 == 4000000000000000.2);
// 輸出 true
// 啊西巴! 什么情況
2.浮點數為什么會不精確?
粗淺的解釋:
1. double 的取值范圍 [-1.79 e+308 , 1.79 e+308] 瞻佛。但 double 的存儲空間為 8 字節(jié)一共 64 位脱篙,也就是說 double 最多可以用 2∧64 個不同值來表示不同區(qū)間,約等于 1.84 e+19 個區(qū)間伤柄。
這也是為什么 40000000.0 f = 40000000.5 f绊困, 4000000000000000.0 = 4000000000000000.2 ,因為他們在同一個區(qū)間适刀。
2. 每一個區(qū)間都會用一個值來顯示秤朗,但這個顯示值(在這個區(qū)間的值都用這個值顯示)與存儲值(存儲在計算機中二進制所表示的值)并不一定相等。(“ 整數 ” 顯示值 = 儲存值笔喉,所以下面只討論 “ 小數 ”)
顯示值? (double) ??儲存值 (double)
0.5 ? ? 0.5
0.4 ? ? 0.40000000000000002220446049250313080847263336181640625
0.25 ? ?0.25
0.2 ? ? 0.200000000000000011102230246251565404236316680908203125
什么時候 顯示值 會與 儲存值 相等呢取视? 這里有一個規(guī)律
有一個小于 1 的小數 d 硝皂,如果滿足 d*2∧n = 1 則 d 的 顯示值 等與 儲存值 。( n 為正整數)?
比如: ?? ? ? ? ? ? ??0.5 * 2 = 1 ,? ? ? ? ? ? ? ?0.25 * 2 * 2 ?= 1 , ? ? ? ? ? ? 0.125 * 2 * 2 * 2 = 1
為什么會有這個規(guī)律贫途,可以了解小數的二進制表示 吧彪。
計算和比較時用的是 儲存值,最終以 計算結果所在區(qū)間的 顯示值 顯示在屏幕上丢早;
顯示值? (double) ? ?儲存值 (double)
0.4 ? ? ?0.40000000000000002220446049250313080847263336181640625
0.2 ? ? ?0.200000000000000011102230246251565404236316680908203125
接下來我們可以解釋:0.4 ? * ? 0.2 ? = ? 0.08000000000000002?
a? = (0.4的儲存值) 0.40000000000000002220446049250313080847263336181640625;
b? = (0.2的儲存值) 0.200000000000000011102230246251565404236316680908203125;
a * b ≈ 0.08000000000000000888178419700125256990808622629275169;
計算結果需要存儲,它所在區(qū)間對應的?儲存值?為 :
0.080000000000000015543122344752191565930843353271484375
該?存儲值 對應的 顯示值 為:
0.08000000000000002
3.誤差累積效應(重點)
浮點數因為計算時是用?儲存值 多次計算秧倾,有可能會放大誤差怨酝。
System.out.println(0.4 * 0.4 * 0.4); // 輸出 0.06400000000000002
System.out.println(0.5 * 0.5 * 0.5); // 輸出 0.125
注意是有可能不是一定,有兩種情況那先;
1. (顯示值? =? 儲存值) 农猬,這樣多次計算沒有誤差,除非計算中出現數值的有效位數過長售淡,double類型不能表示斤葱,會出現精度丟失的情況
2. (顯示值? >? 儲存值) 與 (顯示值? < ?儲存值)混合運算,它們的誤差會相互抵消
3.浮點數使用場景
一般來說浮點數使用比較方便揖闸,誤差比較小( 可以忽略 )揍堕,計算機操作浮點數的效率高( 與BigDecimal相比較),所以浮點數挺常用的汤纸。但使用它必須?慎重?考慮以下情況:
1. 數值的有效位數過長衩茸,會發(fā)生精度丟失;
2. 數值較大贮泞,誤差也會變大了楞慈;
? ? 0.40000000000000000000000? ? =? ? 0.40000000000000002220446
(在同一個區(qū)間,儲存值都為 ?0.40000000000000002220446049250313080847263336181640625 ?所以相等)
? ? 40000000000000000000000.0 ? ? = ? 40000000000000002220446.0
(在同一個區(qū)間啃擦,儲存值都為? 40000000000000000000000? 所以相等)
3. 浮點數的?誤差累積效應囊蓝;
4. 該數值表示的類型是否對誤差有嚴格的要求,(比如:時間令蛉,金錢)
4.該如何進行精確的計算
Java要進行精確計算聚霜,需要使用到類java.math.Big。借助BigDecima可以查看浮點數的儲存值言询;
BigDecimal b1 = new BigDecimal( 0.4 ); // double類型字面量
BigDecimal b2 = new BigDecimal( 0.4f ); // float類型字面量
System.out.println( b1 ); // 輸出 0.40000000000000002220446049250313080847263336181640625
System.out.println( b2 ); // 輸出 0.4000000059604644775390625
哇卡卡俯萎,通過上面的案例,可以發(fā)現 BigDecima 真強大T撕肌夫啊!不過用它做計算有點麻煩
publicBigDecimal add(BigDecimal value);//加法
publicBigDecimal subtract(BigDecimal value);//減法
publicBigDecimal multiply(BigDecimal value);//乘法
publicBigDecimal divide(BigDecimal value);//除法
BigDecimal b1 = new BigDecimal("0.4");
BigDecimal b2 = new BigDecimal("0.2");
System.out.println(b1.add(b2)); // 輸出 0.6
System.out.println(b1.subtract(b2)); // 輸出 0.2
System.out.println(b1.multiply(b2)); // 輸出 0.08
System.out.println(b1.divide(b2)); // 輸出 2
若要詳細了解BigDecima,請查看API辆憔。