一、float整數計算偏差html
案例:會員積分字段採用float類型,致使計算會員積分時,7位整數的數據計算結果出現偏差。java
緣由:超出float精度範圍,沒法精確計算。mysql
float和double的精度是由尾數的位數來決定的。浮點數在內存中是按科學計數法來存儲的,其整數部分始終是一個隱含着的「1」,因爲它是不變的,故不能對精度形成影響。sql
float:2^23 = 8388608,一共七位,這意味着最多能有7位有效數字,但絕對能保證的爲6位,也即float的精度爲6~7位有效數字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度爲15~16位。數據庫
難道只是位數多大的問題,字段類型換成double就能夠解決嗎?對於本案例是這樣,由於都是整數計算,但若是有小數位,就不必定了,見下面案例。編程
二、double小數轉bigdecimal後四捨五入計算有偏差app
案例:less
double g= 12.35;
編程語言
BigDecimal bigG=new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); //指望獲得12.4
ide
System.out.println("test G:"+bigG.doubleValue());
test G:12.3
緣由:
定義double g= 12.35; 而在計算機中二進制表示可能這是樣:定義了一個g=12.34444444444444449,
new BigDecimal(g) g仍是12.34444444444444449
new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); 獲得12.3
正確的定義方式是使用字符串構造函數:
new BigDecimal("12.35").setScale(1, BigDecimal.ROUND_HALF_UP)
三、float和double作四則運算偏差
案例:
public class Test{
public static void main(String args[]){
System.out.println(0.05+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(123.3/100);
}
}
結果:
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
緣由:
那麼爲何會出現精度丟失呢?在查閱了一些資料之後,我稍微有了一些頭緒,下面是本人的愚見,僅供參考。
首先得從計算機自己去討論這個問題。咱們知道,計算機並不能識別除了二進制數據之外的任何數據。不管咱們使用何種編程語言,在何種編譯環境下工做,都要先 把源程序翻譯成二進制的機器碼後才能被計算機識別。以上面提到的狀況爲例,咱們源程序裏的2.4是十進制的,計算機不能直接識別,要先編譯成二進制。但問 題來了,2.4的二進制表示並不是是精確的2.4,反而最爲接近的二進制表示是2.3999999999999999。緣由在於浮點數由兩部分組成:指數和尾數,這點若是知道怎樣進行浮點數的二進制與十進制轉換,應該是不難理解的。若是在這個轉換的過程當中,浮點數參與了計算,那麼轉換的過程就會變得不可預 知,而且變得不可逆。咱們有理由相信,就是在這個過程當中,發生了精度的丟失。而至於爲何有些浮點計算會獲得準確的結果,應該也是碰巧那個計算的二進制與 十進制之間可以準確轉換。而當輸出單個浮點型數據的時候,能夠正確輸出,如
double d = 2.4;
System.out.println(d);
輸出的是2.4,而不是2.3999999999999999。也就是說,不進行浮點計算的時候,在十進制裏浮點數能正確顯示。這更印證了我以上的想法,即若是浮點數參與了計算,那麼浮點數二進制與十進制間的轉換過程就會變得不可預知,而且變得不可逆。
事實上,浮點數並不適合用於精確計算,而適合進行科學計算。這裏有一個小知識:既然float和double型用來表示帶有小數點的數,那爲何咱們不稱 它們爲「小數」或者「實數」,要叫浮點數呢?由於這些數都以科學計數法的形式存儲。當一個數如50.534,轉換成科學計數法的形式爲5.053e1,它 的小數點移動到了一個新的位置(即浮動了)。可見,浮點數原本就是用於科學計算的,用來進行精確計算實在太不合適了。
四、bigdecimal構造函數使用不當帶來異常
案例:
BigDecimal
其中一個構造函數以雙精度浮點數做爲輸入,另外一個以整數和換算因子做爲輸入,還有一個以小數的 String
表示做爲輸入。要當心使用 BigDecimal(double)
構造函數,由於若是不瞭解它,會在計算過程當中產生舍入偏差。請使用基於整數或 String
的構造函數。
若是使用 BigDecimal(double)
構造函數不恰當,在傳遞給 JDBC setBigDecimal()
方法時,會形成彷佛很奇怪的 JDBC 驅動程序中的異常。例如,考慮如下 JDBC 代碼,該代碼但願將數字 0.01
存儲到小數字段:
<span style="font-family: 'Microsoft YaHei';">PreparedStatement ps = connection.prepareStatement("INSERT INTO Foo SET name=?, value=?"); ps.setString(1, "penny"); ps.setBigDecimal(2, new BigDecimal(0.01)); ps.executeUpdate();</span> |
在執行這段彷佛無害的代碼時會拋出一些使人疑惑不解的異常(這取決於具體的 JDBC 驅動程序),由於 0.01
的雙精度近似值會致使大的換算值,這可能會使 JDBC 驅動程序或數據庫感到迷惑。JDBC 驅動程序會產生異常,但可能不會說明代碼實際上錯在哪裏,除非意識到二進制浮點數的侷限性。相反,使用 BigDecimal("0.01")
或 BigDecimal(1, 2)
構造 BigDecimal
來避免這類問題,由於這兩種方法均可以精確地表示小數。
在《Effective Java》這本書中也提到這個原則,float和double只能用來作科學計算或者是工程計算,在商業計算中咱們要用java.math.BigDecimal。使用BigDecimal而且必定要用String來夠造。
BigDecimal用哪一個構造函數?
BigDecimal(double val)
BigDecimal(String val)
上面的API簡要描述至關的明確,並且一般狀況下,上面的那一個使用起來要方便一些。咱們可能想都不想就用上了,會有什麼問題呢?等到出了問題的時候,才發現參數是double的構造方法的詳細說明中有這麼一段:
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.
原來咱們若是須要精確計算,非要用String來夠造BigDecimal不可!
六、定點數和浮點數的區別
在計算機系統的發展過程當中,曾經提出過多種方法表達實數。典型的好比相對於浮點數的定點數(Fixed Point Number)。在這種表達方式中,小數點固定的位於實數全部數字中間的某個位置。貨幣的表達就可使用這種方式,好比 99.00 或者 00.99 能夠用於表達具備四位精度(Precision),小數點後有兩位的貨幣值。因爲小數點位置固定,因此能夠直接用四位數值來表達相應的數值。SQL 中的 NUMBER 數據類型就是利用定點數來定義的。還有一種提議的表達方式爲有理數表達方式,即用兩個整數的比值來表達實數。
定點數表達法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大的數或者特別小的數。最終,絕大多數現代的計算機系統採納了所謂的浮點數表達方式。這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa ),一個基數(Base),一個指數(Exponent)以及一個表示正負的符號來表達實數。好比 123.45 用十進制科學計數法能夠表達爲 1.2345 × 102 ,其中 1.2345 爲尾數,10 爲基數,2 爲指數。浮點數利用指數達到了浮動小數點的效果,從而能夠靈活地表達更大範圍的實數。
在MySQL中使用浮點數類型和定點數類型來表示小數。浮點數類型包括單精度浮點數(FLOAT型)和雙精度浮點數(DOUBLE型)。定點數類型就是DECIMAL型。MySQL的浮點數類型和定點數類型以下表所示:
類型名稱 | 字節數 | 負數的取值範圍 | 非負數的取值範圍 |
---|---|---|---|
FLOAT | 4 | -3.402823466E+38~ -1.175494351E-38 |
0和1.175494351E-38~ 3.402823466E+38 |
DOUBLE | 8 | -1.7976931348623157E+308~ -2.2250738585072014E-308 |
0和2.2250738585072014E-308~ 1.7976931348623157E+308 |
DECIMAL(M,D)或DEC(M,D) | M+2 | 同DOUBLE型 | 同DOUBLE型 |
從上表中能夠看出,DECIMAL型的取值範圍與DOUBLE相同。可是,DECIMAL的有效取值範圍由M和D決定,並且DECIMAL型的字節數是M+2,也就是說,定點數的存儲空間是根據其精度決定的。
七、bigdecimal比等方法
如浮點類型同樣, BigDecimal
也有一些使人奇怪的行爲。尤爲在使用 equals()
方法來檢測數值之間是否相等時要當心。 equals()
方法認爲,兩個表示同一個數但換算值不一樣(例如, 100.00
和 100.000
)的 BigDecimal
值是不相等的。然而, compareTo()
方法會認爲這兩個數是相等的,因此在從數值上比較兩個 BigDecimal
值時,應該使用 compareTo()
而不是 equals()
。
另外還有一些情形,任意精度的小數運算仍不能表示精確結果。例如, 1
除以 9
會產生無限循環的小數 .111111...
。出於這個緣由,在進行除法運算時, BigDecimal
可讓您顯式地控制舍入。 movePointLeft()
方法支持 10 的冪次方的精確除法。
int r=big_decimal.compareTo(BigDecimal.Zero); //和0,Zero比較
if(r==0) //等於
if(r==1) //大於
if(r==-1) //小於
八、簡化bigdecimal計算的小工具類
若是咱們要作一個加法運算,須要先將兩個浮點數轉爲String,而後夠形成BigDecimal,在其中一個上調用add方法,傳入另外一個做爲參數,而後把運算的結果(BigDecimal)再轉換爲浮點數。你可以忍受這麼煩瑣的過程嗎?網上提供的工具類Arith來簡化操做。它提供如下靜態方法,包括加減乘除和四捨五入:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
精確運算:
BigDecimal payroll = new BigDecimal("2.232");
BigDecimal finance = new BigDecimal("3.453");
if(payroll.compareTo(new BigDecimal("3500.00"))>0){
System.err.println(payroll.compareTo(new BigDecimal("3500"))>0);
}else{
System.err.println(payroll.compareTo(new BigDecimal("3500"))>0);
}
BigDecimal balance = new BigDecimal("4.56");
BigDecimal total = payroll.add(finance);
String weString = payroll.add(finance).add(balance).toString();
// payroll.divide(finance)
// payroll.multiply(finance)
// payroll.subtract(finance)
System.out.println("down="+total.setScale(2,BigDecimal.ROUND_HALF_DOWN)+"\tup="+total.setScale
(2,BigDecimal.ROUND_HALF_UP));
參考:
http://justjavac.iteye.com/blog/1073775
http://www.iteye.com/problems/51604
http://blog.163.com/howl_prowler/blog/static/2661971520114553211964/
http://www.cnblogs.com/wingsless/p/3426108.html
http://zhidao.baidu.com/link?url=2L4pkHgVCXlwEeDM0GRHY2gYUwR9d2JC3knqxvHwdyrrdz_LwK92gVAaIy3hhKEQYdUwNjMLe_RJO3cl8sJvbcAnFK-_rMS4Oy_viystUEe