java中金額(浮點表示)的計算
java中進行金額的計算經常浮點數丟失精度,造成這種問題的原因應該與cpu對浮點數的計算方式有關,有下面的介紹:
從原理上來講,任何一門語言對於浮點數的計算都是不精確的。
因為現在的Computer都是基於二進位制數來儲存計算的。
例如計算8+3時,Computer會轉換為二進位制的加法1000+11=1011,然後再轉換為十進位制數為11。
這種演算法對於整數來說是不會產生誤差的(如果不超過計算範圍);
而對於浮點數計算有時就會產生誤差。因為有的浮點數轉換成為二進位制時是一個無窮迴圈小數。
例如十進位制的0.4,轉換成為二進位制為0.0110011001100110....,這樣,在0.4+0.3時就不能準確的算出是0.7,
而是經過一些舍入處理才能得出正確結果,但經過多次運算誤差產生的較大時,
即使經過一些舍入處理也不能得到精確的結果了。
既然這樣,那麼在java中為了保持精度需要怎樣做呢?
一句話:用BigDecimal進行運算;BigDecimal的建構函式有下面幾種:
BigDecimal(BigInteger val)
Translates a BigInteger into a BigDecimal.
BigDecimal(BigInteger unscaledVal, int scale)
Translates a BigInteger unscaled value and an int scale into a BigDecimal.
BigDecimal(double val)
Translates a double into a BigDecimal.
BigDecimal(String val)
Translates the String representation of a BigDecimal into a BigDecimal.
按照jdk幫助文件說明,如果採用double型別建構函式的話有可能丟失精度,推薦採用String型別的建構函式.
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 notwithstanding.
在用的過程中寫了一個幫助類Amount,以解決在日常開發中遇到的金額計算問題:
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 金額型別. 支援自身的四則運算,並將this返回.
*/
publicclass Amount implements Serializable {
private BigDecimal value;
/**
* 提供預設精度10
*/
privateint scale =10;
* double型別建構函式
*
* @param value
*/
public Amount(double value) {
this.value =new BigDecimal(Double.toString(value));
}
/**
* String型別建構函式
*
* @param value
*/
public Amount(String value) {
this.value =new BigDecimal(value);
}
* 取得BigDecimal的值
*
* @return
*/
public BigDecimal getValue() {
returnthis.value;
}
/**
* 兩個double型別的數值相加
*
* @param v1
* @param v2
* @return
*/
publicdouble add(double v1, double v2) {
Amount a1 =new Amount(v1);
Amount a2 =new Amount(v2);
return add(a1, a2);
}
/**
* 兩數相除
*
* @param v1
* @param v2
* @return
*/
publicdouble div(double v1, double v2) {
Amount a1 =new Amount(v1);
Amount a2 =new Amount(v2);
returnthis.divide(a1, a2);
}
/**
* 相減
*
* @param v1
* @param v2
* @return
*/
publicdouble sub(double v1, double v2) {
Amount a1 =new Amount(v1);
Amount a2 =new Amount(v2);
returnthis.subtract(a1, a2);
}
/**
* 相乘
*
* @param v1
* @param v2
* @return
*/
publicdouble mul(double v1, double v2) {
Amount a1 =new Amount(v1);
Amount a2 =new Amount(v2);
returnthis.multiply(a1, a2);
}
/**
* 兩個Amount型別的資料進行相加
*
* @param v1
* @param v2
* @return
*/
publicdouble add(Amount v1, Amount v2) {
return v1.getValue().add(v2.getValue()).doubleValue();
}
/**
* 兩個Amount型別變數相除
*
* @param v1
* @param v2
* @return
*/
publicdouble divide(Amount v1, Amount v2) {
if (scale <0) {
thrownew IllegalArgumentException("精度指定錯誤,請指定一個>=0的精度");
}
return v1.getValue().divide(v2.getValue(), scale,
BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 兩數相乘
*
* @param v1
* @param v2
* @return
*/
publicdouble multiply(Amount v1, Amount v2) {
return v1.getValue().multiply(v2.getValue()).doubleValue();
}
/**
* 兩數相減
*
* @param v1
* @param v2
* @return
*/
publicdouble subtract(Amount v1, Amount v2) {
return v1.getValue().subtract(v2.getValue()).doubleValue();
}
/**
* 返回value的浮點數值
*
* @return
*/
publicdouble doubleValue() {
returnthis.getValue().doubleValue();
}
/**
* 設定精度
* @param scale
*/
publicvoid setScale(int scale) {
this.scale = scale;
}
}