java中float和double精度問題

背景

在java中float賦值給double,會產生精度問題。

float a = 2.1f;
double b = 3.3;
b = a;
System.out.println(b);

輸出為2.0999999046325684

小數的二進制表示問題

首先我們要搞清楚下面兩個問題:

十進制整數如何轉化為二進制數

算法很簡單。舉個例子,11表示成二進制數:

11/2=5 余 1
5/2=2 余 1
2/2=1 余 0
1/2=0 余 1
0結束
11二進制表示為(從下往上):1011

這里提一點:只要遇到除以后的結果為0了就結束了,大家想一想,所有的整數除以2是不是一定能夠最終得到0。換句話說,所有的整數轉變為二進制數的算法會不會無限循環下去呢?絕對不會,整數永遠可以用二進制精確表示 ,但小數就不一定了。

十進制小數如何轉化為二進制數

算法是乘以2直到沒有了小數為止。舉個例子,0.9表示成二進制數

0.92=1.8 取整數部分 1
0.8(1.8的小數部分)
2=1.6 取整數部分 1
0.62=1.2 取整數部分 1
0.2
2=0.4 取整數部分 0
0.42=0.8 取整數部分 0
0.8
2=1.6 取整數部分 1
0.6*2=1.2 取整數部分 0
.........
0.9二進制表示為(從上往下): 1100100100100......

注意:上面的計算過程循環了,也就是說*2永遠不可能消滅小數部分,這樣算法將無限下去。很顯然,小數的二進制表示有時是不可能精確的 。其實道理很簡單,十進制系統中能不能準確表示出1/3呢?同樣二進制系統也無法準確表示1/10。這也就解釋了為什么浮點型減法出現了"減不盡"的精度丟失問題。

float型在內存中的存儲

眾所周知、 Java 的float型在內存中占4個字節。float的32個二進制位結構如下
float內存存儲結構

4bytes 31 30 29----23 22----0
表示 實數符號位 指數符號位 指數位 有效數位

其中符號位1表示正,0表示負。有效位數位24位,其中一位是實數符號位。
將一個float型轉化為內存存儲格式的步驟為:

  • 先將這個實數的絕對值化為二進制格式,注意實數的整數部分和小數部分的二進制方法在上面已經探討過了。
  • 將這個二進制格式實數的小數點左移或右移n位,直到小數點移動到第一個有效數字的右邊。
  • 從小數點右邊第一位開始數出二十三位數字放入第22到第0位。
  • 如果實數是正的,則在第31位放入“0”,否則放入“1”。
  • 如果n 是左移得到的,說明指數是正的,第30位放入“1”。如果n是右移得到的或n=0,則第30位放入“0”。
  • 如果n是左移得到的,則將n減去1后化為二進制,并在左邊加“0”補足七位,放入第29到第23位。如果n是右移得到的或n=0,則將n化為二進制后在左邊加“0”補足七位,再各位求反,再放入第29到第23位。

舉例說明: 11.9的內存存儲格式

  • 將11.9化為二進制后大約是" 1011. 1110011001100110011001100..."。
  • 將小數點左移三位到第一個有效位右側: "1. 011 11100110011001100110 "。 保證有效位數24位,右側多余的截取(誤差在這里產生了 )。
  • 這已經有了二十四位有效數字,將最左邊一位“1”去掉,得到“ 011 11100110011001100110 ”共23bit。將它放入float存儲結構的第22到第0位。
  • 因為11.9是正數,因此在第31位實數符號位放入“0”。
  • 由于我們把小數點左移,因此在第30位指數符號位放入“1”。
  • 因為我們是把小數點左移3位,因此將3減去1得2,化為二進制,并補足7位得到0000010,放入第29到第23位。
  • 最后表示11.9為: 0 1 0000010 011 11100110011001100110

再舉一個例子:0.2356的內存存儲格式

  • 將0.2356化為二進制后大約是0.00111100010100000100100000。
  • 將小數點右移三位得到1.11100010100000100100000。
  • 從小數點右邊數出二十三位有效數字,即11100010100000100100000放
    入第22到第0位。
  • 由于0.2356是正的,所以在第31位放入“0”。
  • 由于我們把小數點右移了,所以在第30位放入“0”。
  • 因為小數點被右移了3位,所以將3化為二進制,在左邊補“0”補足七
    位,得到0000011,各位取反,得到1111100,放入第29到第23位。
  • 最后表示0.2356為:0 0 1111100 11100010100000100100000

將一個內存存儲的float二進制格式轉化為十進制的步驟:

  • 將第22位到第0位的二進制數寫出來,在最左邊補一位“1”,得到二十四位有效數字。將小數點點在最左邊那個“1”的右邊。
  • 取出第29到第23位所表示的值n。當30位是“0”時將n各位求反。當30位是“1”時將n增1。
  • 將小數點左移n位(當30位是“0”時)或右移n位(當30位是“1”時),得到一個二進制表示的實數。
  • 將這個二進制實數化為十進制,并根據第31位是“0”還是“1”加上正號或負號即可。

浮點型的減法運算

  • 0操作數的檢查;
    如果判斷兩個需要加減的浮點數有一個為0,即可得知運算結果而沒有必要再進行有序的一些列操作。

  • 比較階碼(指數位)大小并完成對階;
    兩浮點數進行加減,首先要看兩數的指數位是否相同,即小數點位置是否對齊。若兩數 指數位 相同,表示小數點是對齊的,就可以進行尾數的加減運算。反之,若兩數階碼不同,表示小數點位置沒有對齊,此時必須使兩數的階碼相同,這個過程叫做對階 。
    如何對 階(假設兩浮點數的指數位為 Ex 和 Ey ):通過尾數的移位以改變 Ex 或 Ey ,使之相等。 由 于浮點表示的數多是規格化的,尾數左移會引起最高有位的丟失,造成很大誤差;而尾數右移雖引起最低有效位的丟失,但造成的誤差較小,因此,對階操作規定使尾數右移,尾數右移后使階碼作相應增加,其數值保持不變。很顯然,一個增加后的階碼與另一個相等,所增加的階碼一定是小階。因此在對階時,總是使小階向大階看齊 ,即小階的尾數向右移位 ( 相當于小數點左移 ) ,每右移一位,其階碼加 1 ,直到兩數的階碼相等為止,右移的位數等于階差 △ E 。

  • 尾數(有效數位)進行加或減運算;

  • 結果規格化并進行舍入處理。

參考文章一

C語言和C#語言中,對于浮點類型的數據采用單精度類型(float)和雙精度類型(double)來存儲,float數據占用32bit,double數據占用64bit,我們在聲明一個變量float f= 2.25f的時候,是如何分配內存的呢?如果胡亂分配,那世界豈不是亂套了么,其實不論是float還是double在存儲方式上都是遵從IEEE的規范 的,float遵從的是IEEE R32.24 ,而double 遵從的是R64.53。

無論是單精度還是雙精度在存儲中都分為三個部分:

  • 符號位(Sign) : 0代表正,1代表為負
  • 指數位(Exponent):用于存儲科學計數法中的指數數據,并且采用移位存儲
  • 尾數部分(Mantissa):尾數部分

其中float的存儲方式如下圖所示:


而雙精度的存儲方式為:

R32.24和R64.53的存儲方式都是用科學計數法來存儲數據的,比如8.25用十進制的科學計數法表示就為:8.25x10的0次方,而120.5可以表示為:1.205x10的二次方, 這些小學的知識就不用多說了吧。而我們傻蛋計算機根本不認識十進制的數據,他只認識0,1,所以在計算機存儲中,首先要將上面的數更改為二進制的科學計數 法表示,8.25用二進制表示可表示為1000.01,我靠,不會連這都不會轉換吧?那我估計要沒轍了。120.5用二進制表示為:1110110.1用二進制的科學計數法表示1000.01可以表示為1.0001x2的三次方,1110110.1可以表示為1.1101101x2的六次方,任何一個數都的科學計數法表示都為1.xxx乘以2的n次方,尾數部分就可以表示為xxxx,第一位都是1嘛,干嘛還要表示呀?可以將小數點前面的1省略,所以23bit的尾數部分,可以表示的精度卻變成了24bit,道理就是在這里,那24bit能精確到小數點后幾位呢,我們知道9的二進制表示為1001,所以4bit能精確十進制中的1位小數點, 24bit就能使float能精確到小數點后6位,而對于指數部分,因為指數可正可負,8位的指數位能表示的指數范圍就應該為:-127-128了,所以 指數部分的存儲采用移位存儲,存儲的數據為元數據+127,下面就看看8.25和120.5在內存中真正的存儲方式。

首先看下8.25,用二進制的科學計數法表示為:1.0001x2的三次方。按照上面的存儲方式,符號位為:0,表示為正,指數位為:3+127=130,位數部分為,故8.25的存儲方式如下圖所示:


而單精度浮點數120.5的存儲方式如下圖所示:


那么如果給出內存中一段數據,并且告訴你是單精度存儲的話,你如何知道該數據的十進制數值呢?其實就是對上面的反推過程,比如給出如下內存 數據:0100001011101101000000000000,首先我們現將該數據分段,0 10000 0101 110 1101 0000 0000 0000 0000,在內存中的存儲就為下圖所示:

根據我們的計算方式,可以計算出,這樣一組數據表示為:1.1101101x2的六次方=120.5

而雙精度浮點數的存儲和單精度的存儲大同小異,不同的是指數部分和尾數部分的位數。所以這里不再詳細的介紹雙精度的存儲方式了,只將120.5的最后存儲方式圖給出,大家可以仔細想想為何是這樣子的

下面我就這個基礎知識點來解決一個我們的一個疑惑,請看下面一段程序,注意觀察輸出結果

float f = 2.2f;
double d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
f = 2.25f;
d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));

可能輸出的結果讓大家疑惑不解,單精度的2.2轉換為雙精度后,精確到小數點后13位后變為了2.2000000476837,而單精度的2.25轉換為雙精度后,變為了2.2500000000000,為何2.2在轉換后的數值更改了而2.25卻沒有更改呢?很奇怪吧?其實通過上面關于兩 種存儲結果的介紹,我們已經大概能找到答案。首先我們看看2.25的單精度存儲方式,很簡單 0 1000 0001 001 0000 0000 0000 0000 0000,而2.25的雙精度表示為:0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,這樣2.25在進行強制轉換的時候,數值是不會變的,而我們再看看2.2呢,2.2用科學計數法表示應該為:將十進制的小數轉換為二進制的小數 的方法為將小數2,取整數部分,所以0.282=0.4,所以二進制小數第一位為0.4的整數部分0,0.4×2=0.8,第二位為0,0.82= 1.6,第三位為1,0.6×2 = 1.2,第四位為1,0.2*2=0.4,第五位為0,這樣永遠也不可能乘到=1.0,得到的二進制是一個無限循環的排列 00110011001100110011... ,對于單精度數據來說,尾數只能表示24bit的精度,所以2.2的float存儲為:

但是這樣存儲方式,換算成十進制的值,卻不會是2.2的,應為十進制在轉換為二進制的時候可能會不準確,如2.2,而double類型的數 據也存在同樣的問題,所以在浮點數表示中會產生些許的誤差,在單精度轉換為雙精度的時候,也會存在誤差的問題,對于能夠用二進制表示的十進制數據,如 2.25,這個誤差就會不存在,所以會出現上面比較奇怪的輸出結果。

本文屬作者原創,只發布在博客園,希望大家在轉載的時候,注明出處和作者,謝謝。

注:本文在寫作過程中,參照了如下資料:

http://www.msdn.net/library/chs/default.asp?url=/library/CHS/vccore/html/_core_why_floating_point_numbers_may_lose_precision.asp

http://blog.csdn.net/ganxingming/archive/2006/12/19/1449526.asp

參考資料二

  1. float整數計算誤差

案例:會員積分字段采用float類型,導致計算會員積分時,7位整數的數據計算結果出現誤差。
原因:超出float精度范圍,無法精確計算。
float和double的精度是由尾數的位數來決定的。浮點數在內存中是按科學計數法來存儲的,其整數部分始終是一個隱含著的“1”,由于它是不變的,故不能對精度造成影響。
float:2^23 = 8388608,一共七位,這意味著最多能有7位有效數字,但絕對能保證的為6位,也即float的精度為6~7位有效數字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度為15~16位。
難道只是位數多大的問題,字段類型換成double就可以解決嗎?對于本案例是這樣,因為都是整數計算,但如果有小數位,就不一定了,見下面案例。

  1. double小數轉bigdecimal后四舍五入計算有誤差

案例:
double g= 12.35;
BigDecimal bigG=new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); //期望得到12.4
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)

  1. 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,它 的小數點移動到了一個新的位置(即浮動了)。可見,浮點數本來就是用于科學計算的,用來進行精確計算實在太不合適了。

  1. bigdecimal構造函數使用不當帶來異常

案例:
BigDecimal其中一個構造函數以雙精度浮點數作為輸入,另一個以整數和換算因子作為輸入,還有一個以小數的 String 表示作為輸入。要小心使用 BigDecimal(double) 構造函數,因為如果不了解它,會在計算過程中產生舍入誤差。請使用基于整數或 String 的構造函數。
如果使用 BigDecimal(double) 構造函數不恰當,在傳遞給 JDBC setBigDecimal() 方法時,會造成似乎很奇怪的 JDBC 驅動程序中的異常。例如,考慮以下 JDBC 代碼,該代碼希望將數字 0.01 存儲到小數字段:

PreparedStatement ps =
connection.prepareStatement("INSERT INTO Foo SET name=?, value=?");
ps.setString(1, "penny");
ps.setBigDecimal(2, new BigDecimal(0.01));
ps.executeUpdate();

在執行這段似乎無害的代碼時會拋出一些令人迷惑不解的異常(這取決于具體的 JDBC 驅動程序),因為 0.01 的雙精度近似值會導致大的換算值,這可能會使 JDBC 驅動程序或數據庫感到迷惑。JDBC 驅動程序會產生異常,但可能不會說明代碼實際上錯在哪里,除非意識到二進制浮點數的局限性。相反,使用 BigDecimal("0.01") 或 BigDecimal(1,2) 構造 BigDecimal 來避免這類問題,因為這兩種方法都可以精確地表示小數。

  1. 解決浮點數精確計算有誤差的方法

在《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不可!

  1. 定點數和浮點數的區別

在計算機系統的發展過程中,曾經提出過多種方法表達實數。典型的比如相對于浮點數的定點數(Fixed Point Number)。在這種表達方式中,小數點固定的位于實數所有數字中間的某個位置。貨幣的表達就可以使用這種方式,比如 99.00 或者 00.99 可以用于表達具有四位精度(Precision),小數點后有兩位的貨幣值。由于小數點位置固定,所以可以直接用四位數值來表達相應的數值。SQL 中的 NUMBER 數據類型就是利用定點數來定義的。還有一種提議的表達方式為有理數表達方式,即用兩個整數的比值來表達實數。

定點數表達法的缺點在于其形式過于僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利于同時表達特別大的數或者特別小的數。最終,絕大多數現代的計算機系統采納了所謂的浮點數表達方式。這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa ),一個基數(Base),一個指數(Exponent)以及一個表示正負的符號來表達實數。比如 123.45 用十進制科學計數法可以表達為 1.2345 × 102 ,其中 1.2345 為尾數,10 為基數,2 為指數。浮點數利用指數達到了浮動小數點的效果,從而可以靈活地表達更大范圍的實數。

類型名稱 字節數 負數的取值范圍 非負數的取值范圍
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型|

在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,也就是說,定點數的存儲空間是根據其精度決定的。

  1. 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) //小于

  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)

import java.math.BigDecimal;    
  
/**  
 * 進行BigDecimal對象的加減乘除,四舍五入等運算的工具類  
 * @author ameyume  
 *  
 */  
public class Arith {    
  
    /**   
    * 由于Java的簡單類型不能夠精確的對浮點數進行運算,這個工具類提供精   
    * 確的浮點數運算,包括加減乘除和四舍五入。   
    */    
    //默認除法運算精度    
    private static final int DEF_DIV_SCALE = 10;    
           
    //這個類不能實例化    
    private Arith(){    
    }    
  
    /**   
     * 提供精確的加法運算。   
     * @param v1 被加數   
     * @param v2 加數   
     * @return 兩個參數的和   
     */    
    public static double add(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.add(b2).doubleValue();    
    }    
       
    /**   
     * 提供精確的減法運算。   
     * @param v1 被減數   
     * @param v2 減數   
     * @return 兩個參數的差   
     */    
    public static double sub(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.subtract(b2).doubleValue();    
    }    
       
    /**   
     * 提供精確的乘法運算。   
     * @param v1 被乘數   
     * @param v2 乘數   
     * @return 兩個參數的積   
     */    
    public static double mul(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.multiply(b2).doubleValue();    
    }    
  
    /**   
     * 提供(相對)精確的除法運算,當發生除不盡的情況時,精確到   
     * 小數點以后10位,以后的數字四舍五入。   
     * @param v1 被除數   
     * @param v2 除數   
     * @return 兩個參數的商   
     */    
    public static double div(double v1,double v2){    
        return div(v1,v2,DEF_DIV_SCALE);    
    }    
  
    /**   
     * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指   
     * 定精度,以后的數字四舍五入。   
     * @param v1 被除數   
     * @param v2 除數   
     * @param scale 表示表示需要精確到小數點以后幾位。   
     * @return 兩個參數的商   
     */    
    public static double div(double v1,double v2,int scale){    
        if(scale<0){    
            throw new IllegalArgumentException(    
                "The scale must be a positive integer or zero");    
        }    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();    
    }    
  
    /**   
     * 提供精確的小數位四舍五入處理。   
     * @param v 需要四舍五入的數字   
     * @param scale 小數點后保留幾位   
     * @return 四舍五入后的結果   
     */    
    public static double round(double v,int scale){    
        if(scale<0){    
            throw new IllegalArgumentException(    
                "The scale must be a positive integer or zero");    
        }    
        BigDecimal b = new BigDecimal(Double.toString(v));    
        BigDecimal one = new BigDecimal("1");    
        return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();    
    }    
       
   /**   
    * 提供精確的類型轉換(Float)   
    * @param v 需要被轉換的數字   
    * @return 返回轉換結果   
    */    
    public static float convertsToFloat(double v){    
        BigDecimal b = new BigDecimal(v);    
        return b.floatValue();    
    }    
       
    /**   
    * 提供精確的類型轉換(Int)不進行四舍五入   
    * @param v 需要被轉換的數字   
    * @return 返回轉換結果   
    */    
    public static int convertsToInt(double v){    
        BigDecimal b = new BigDecimal(v);    
        return b.intValue();    
    }    
  
    /**   
    * 提供精確的類型轉換(Long)   
    * @param v 需要被轉換的數字   
    * @return 返回轉換結果   
    */    
    public static long convertsToLong(double v){    
        BigDecimal b = new BigDecimal(v);    
        return b.longValue();    
    }    
  
    /**   
    * 返回兩個數中大的一個值   
    * @param v1 需要被對比的第一個數   
    * @param v2 需要被對比的第二個數   
    * @return 返回兩個數中大的一個值   
    */    
    public static double returnMax(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(v1);    
        BigDecimal b2 = new BigDecimal(v2);    
        return b1.max(b2).doubleValue();    
    }    
  
    /**   
    * 返回兩個數中小的一個值   
    * @param v1 需要被對比的第一個數   
    * @param v2 需要被對比的第二個數   
    * @return 返回兩個數中小的一個值   
    */    
    public static double returnMin(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(v1);    
        BigDecimal b2 = new BigDecimal(v2);    
        return b1.min(b2).doubleValue();    
    }    
  
    /**   
    * 精確對比兩個數字   
    * @param v1 需要被對比的第一個數   
    * @param v2 需要被對比的第二個數   
    * @return 如果兩個數一樣則返回0,如果第一個數比第二個數大則返回1,反之返回-1   
    */    
        public static int compareTo(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(v1);    
        BigDecimal b2 = new BigDecimal(v2);    
        return b1.compareTo(b2);    
    }   
  
}  

參考:
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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容