java中Double類型運算精度丟失問題,(小數(shù)點多出99999999999999)x
發(fā)布時間:2020-09-07 來源: 精準扶貧 點擊:
java 中 中 Double 類型的運算精度丟失的問題 ( 小數(shù)點多出99999999999999)
在使用 Java,double 進行運算時,經(jīng)常出現(xiàn)精度丟失的問題,總是在一個正確的結(jié)果左右偏 0.0000**1。
特別在實際項目中,通過一個公式校驗該值是否大于 0,如果大于 0 我們會做一件事情,小于 0 我們又處理其他事情。
這樣的情況通過double 計算出來的結(jié)果去和 0 比較大小,尤其是有小數(shù)點的時候,經(jīng)常會因為精度丟失而導(dǎo)致程序處理流程出錯。
首先貼一個使用的代碼:
/**
* 將 double 類型數(shù)據(jù)轉(zhuǎn)為字符串(如將 18.4 轉(zhuǎn)為 1840,如果需要 1840.0,把 int 強轉(zhuǎn)去掉即可)
* @param d
* @return
*/
public static String double2String(double d){
BigDecimal bg = new BigDecimal(d * 100);
double doubleValue = bg.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
return
String.valueOf((int)doubleValue);
}
BigDecimal 在《Effective Java》這本書中也提到這個原則,float 和 double 只能用來做科學(xué)計算或者是工程計算,在商業(yè)計算中我們要用 java.math.BigDecimal。BigDecimal 一共有 4 個夠造方法,我們不關(guān)心用 BigInteger 來夠造的那兩個,那么還有兩個, 它們是:
BigDecimal(double val)
Translates a double into a BigDecimal.
BigDecimal(String val)
Translates the String repre sentation of a BigDecimal into a BigDecimal. 上面的 API 簡要描述相當(dāng)?shù)拿鞔_,而且通常情況下,上面的那一個使用起來要方便一些。我們可能想都不想就用上了,會有什么問題呢?等到出了問題的時候,才發(fā)現(xiàn)上面哪個夠造方法的詳細說明中有這么一段:
Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of
any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one. 原來我們?nèi)绻枰_計算,非要用 String 來夠造 BigDecimal 不可!在《Effective Java》一書中的例子是用 String 來夠造 BigDecimal 的,但是書上卻沒有強調(diào)這一點,這也許是一個小小的失誤吧。
解決方案 現(xiàn)在我們已經(jīng)可以解決這個問題了,原則是使用 BigDecimal 并且一定要用 String來夠造。
但是想像一下吧,如果我們要做一個加法運算,需要先將兩個浮點數(shù)轉(zhuǎn)為 String,然后夠造成 BigDecimal,在其中一個上調(diào)用 add 方法,傳入另 一個作為參數(shù),然后把運算的結(jié)果(BigDecimal)再轉(zhuǎn)換為浮點數(shù)。你能夠忍受這么煩瑣的過程嗎?下面我們提供一個工具類 Arith 來簡化操作。它 提供以下靜態(tài)方法,包括加減乘除和四舍五入:
add(double v1,double v2) sub(double v1,double v2) mul(double v1,double v2) ) div(double v1,double v2,int scale) public static double round(double v,int scale)
所以一般對 double 類型進行運算時,做好對結(jié)果進行處理,然后拿這個值去做其他事情。
使用如下:
/**
對 double 數(shù)據(jù)進行取精度.
value
double 數(shù)據(jù).
scale
精度位數(shù)(保留的小數(shù)位數(shù)).
param roundingMode
精度取值方式.
* @return 精度計算后的數(shù)據(jù).
*/
public static double round(double value, int scale,
int roundingMode) {
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(scale, roundingMode);
double d = bd.doubleValue();
bd = null;
return d;
}
/**
double 相加
1
param d2
* @return
*/
public double sum(double d1,double d2){
1 1
BigDecimal bd2 = new BigDecimal(Double.toString(d2));
return bd1.add(bd2).doubleValue();
}
/**
double 相減
1
param d2
* @return
*/
public double sub(double d1,double d2){
1 1
BigDecimal bd2 = new BigDecimal(Double.toString(d2));
return bd1.subtract(bd2).doubleValue();
}
/**
double 乘法
1
param d2
* @return
*/
public double mul(double d1,double d2){
1 1
BigDecimal bd2 = new BigDecimal(Double.toString(d2));
return bd1.multiply(bd2).doubleValue();
}
/**
double 除法
1
* @param d2
param scale 四舍五入 小數(shù)點位數(shù)
* @return
*/
public double div(double d1,double d2,int scale){
當(dāng)然在此之前,你要判斷分母是否為 0,
//
為 0 你可以根據(jù)實際需求做相應(yīng)的處理
1 1
BigDecimal bd2 = new BigDecimal(Double.toString(d2));
return bd1.divide
(bd2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
這樣,計算 double 類型的數(shù)據(jù)計算問題就可以處理了。
另外補充一下 JavaScript 四舍五入的方法:
小數(shù)點問題
Math.round(totalAmount*100)/100 (保留 2 位)
function formatFloat(src, pos)
{
return Math.round(src*Math.pow(10, pos))/Math.pow(10, pos);
}
四舍五入是我們小學(xué)的數(shù)學(xué)問題,這個問題對于我們程序猿來說就類似于 1 到 10的加減乘除那么簡單了。在講解之間我們先看如下一個經(jīng)典的案例:
[java] view plaincopyprint? 1 public static void maiString[] args) {
2
12.5 的四舍五入值:" + Math.round(12.5));
3
System.out.println("-12.5 的四舍五入值:" + Math.round(-12.5));
4
}
5 Output:
6 12.5 的四舍五入值:13
7. -12.5 的四舍五入值:-12
這是四舍五入的經(jīng)典案例,也是我們參加校招時候經(jīng)常會遇到的(貌似我參加筆試的時候遇到過好多次)。從這兒結(jié)果中我們發(fā)現(xiàn)這兩個絕對值相同的數(shù)字,為何近似值會不同呢?其實這與 Math.round 采用的四舍五入規(guī)則來決定。
四舍五入其實在金融方面運用的非常多,尤其是銀行的利息。我們都知道銀行的盈利渠道主要是利息差,它從儲戶手里收集資金,然后放貸出去,期間產(chǎn)生的利息差就是銀行所獲得的利潤。如果我們采用平常四舍五入的規(guī)則話,這里采用每10 筆存款利息計算作為模型,如下:
四舍:0.000、0.001、0.002、0.003、0.004。這些舍的都是銀行賺的錢。
五入:0.005、0.006、0.007、0.008、0.009。這些入的都是銀行虧的錢,分別為:0.005、0.004、.003、0.002、0.001。
所以對于銀行來說它的盈利應(yīng)該是 0.000 + 0.001 + 0.002 + 0.003 + 0.004 - 0.005 - 0.004 - 0.003 - 0.002 - 0.001 = -0.005。從結(jié)果中可以看出每 10 筆的利息銀行可能就會損失 0.005 元,千萬別小看這個數(shù)字,這對于銀行來說就是一筆非常大的損失。面對這個問題就產(chǎn)生了如下的銀行家涉入法了。該算法是由美國銀行家提出了,主要用于修正采用上面四舍五入規(guī)則而產(chǎn)生的誤差。如下:
舍去位的數(shù)值小于 5 時,直接舍去。
舍去位的數(shù)值大于 5 時,進位后舍去。
當(dāng)舍去位的數(shù)值等于 5 時,若 5 后面還有其他非 0 數(shù)值,則進位后舍去,若 5后面是 0 時,則根據(jù) 5 前一位數(shù)的奇偶性來判斷,奇數(shù)進位,偶數(shù)舍去。
對于上面的規(guī)則我們舉例說明
11.556 = 11.56 ------六入
11.554 = 11.55 -----四舍
11.5551 = 11.56 -----五后有數(shù)進位
11.545 = 11.54 -----五后無數(shù),若前位為偶數(shù)應(yīng)舍去
11.555 = 11.56 -----五后無數(shù),若前位為奇數(shù)應(yīng)進位
下面實例是使用銀行家舍入法:
[java] view plaincopyprint? 1 public static void main(String[] args) {
2
d = new BigDecimal(100000);
//存款
3.
BigDecimal r = new BigDecimal(0.001875*3);
//利息
4.
BigDecimal i = d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);
//使用銀行家算法
5
6
System.out.println("季利息是:"+i);
7
}
8 Output:
9. 季利息是:562.50
在上面簡單地介紹了銀行家舍入法,目前 java 支持 7 中舍入法:
1、 ROUND_UP:遠離零方向舍入。向絕對值最大的方向舍入,只要舍棄位非0 即進位。
2、 ROUND_DOWN:趨向零方向舍入。向絕對值最小的方向輸入,所有的位都要舍棄,不存在進位情況。
3、 ROUND_CEILING:向正無窮方向舍入。向正最大方向靠攏。若是正數(shù),舍入行為類似于 ROUND_UP,若為負數(shù),舍入行為類似于 ROUND_DOWN。Math.round()方法就是使用的此模式。
4、 ROUND_FLOOR:向負無窮方向舍入。向負無窮方向靠攏。若是正數(shù),舍入行為類似于 ROUND_DOWN;若為負數(shù),舍入行為類似于 ROUND_UP。
5、 HALF_UP:最近數(shù)字舍入(5 進)。這是我們最經(jīng)典的四舍五入。
6、 HALF_DOWN:最近數(shù)字舍入(5 舍)。在這里 5 是要舍棄的。
7、 HAIL_EVEN:銀行家舍入法。
提到四舍五入那么保留位就必不可少了,在 java 運算中我們可以使用多種方式來實現(xiàn)保留位。
保留位
方法一:四舍五入 方法一:四舍五入
[java] view plaincopyprint? 1 double
f
=
111231.5585;
2 BigDecimal
b
=
new
BigDecimal(f);
3. double
f1
=
b.setScale(2,
RoundingMode.HALF_UP).doubleValue();
在這里使用 BigDecimal ,并且采用 setScale 方法來設(shè)置精確度,同時使用RoundingMode.HALF_UP 表示使用最近數(shù)字舍入法則來近似計算。在這里我們可以看出 BigDecimal 和四舍五入是絕妙的搭配。
方式二:
方式二:
[java] view plaincopyprint? 1 java.text.DecimalFormat
df
=new
java.text.DecimalFormat(”#.00″);
2. df.format(你要格式化的數(shù)字);
例:new java.text.DecimalFormat(”#.00″).format(3.1415926)
#.00 表示兩位小數(shù) #.0000 四位小數(shù) 以此類推…
方式三:
方式三:
[java] view plaincopyprint? 1 double d = 3.1415926;
2
3 String result = String .format(”%.2f”);
4
5. %.2f %. 表示 小數(shù)點前任意位數(shù)
2 表示兩位小數(shù) 格式后的結(jié)果為 f 表示浮點型。
方式四:
方式四:
此外如果使用 struts 標(biāo)簽做輸出的話,有個 format 屬性,設(shè)置為 format="0.00"就是保留兩位小數(shù)
例如:
[java] view plaincopyprint? 1 <bean:write name="entity" property="dkhAFSumPl"
format="0.00" />
2
3 或者
4
5. <fmt:formatNumber type="number" value="${10000.22/100}" maxFractionDigits="0"/>
6
7. maxFractionDigits 表示保留的位數(shù)
BigDecimal.setScale 處理 java 小數(shù)點 BigDecimal.setScale()方法用于格式化小數(shù)點 setScale(1)表示保留一位小數(shù),默認用四舍五入方式
DOWN)直接刪除多余的小數(shù)位,如 2.35 會變成 2.3
UP)進位處理,2.35 變成 2.4
(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35 變成 2.4 setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35 變成 2.3,如果是 5則向下舍
注釋:
注釋:
1:
ale 指的是你小數(shù)點后的位數(shù)。比如 123.456 則 score 就是 3. score()就是 BigDecimal 類中的方法啊。
比如:BigDecimal b = new BigDecimal("123.456"); b.scale(),返回的就是 3.
2:
。
roundingMode 是小數(shù)的保留模式。它們都是 BigDecimal 中的常量字段,有很多種。
比如:BigDecimal.ROUND_HALF_UP 表示的就是 4 舍 5 入。
3:
pubilc BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 的意思是說:我用一個 BigDecimal 對象除以 divisor 后的結(jié)果,并且要求這個結(jié)果保留有 scale 個小數(shù)位,roundingMode 表示的就是保留模式是什么,是四舍五入啊還是其它的,你可以自己選!
4:對于一般 :對于一般 add 、subtract 、multiply 方法的小數(shù)位格式化如下:
方法的小數(shù)位格式化如下:
BigDecimal mData = new BigDecimal("9.655").setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("mData=" + mData);
----結(jié)果:
結(jié)果:----- mData=9.66
熱點文章閱讀