說在前面的話
先祝各位看官在虎年里虎虎生威,財源廣進,萬事如意!
Java 語言支持兩種基本的浮點類型: float
和 double
,以及與它們對應的包裝類 Float
和 Double
。它們都依據 IEEE 754 標準,該標準為 32 位浮點和 64 位雙精度浮點二進制小數定義了二進制標準。
但是在一些項目中,一些非整數值(如幾元和幾分這樣的小數)需要很精確。所以,不要用浮點數表示精確值。浮點數不是精確值,所以使用它們會導致舍入誤差。因此,使用浮點數來試圖表示象貨幣量這樣的精確數量不是一個好的想法。使用浮點數來進行元和分計算會得到災難性的后果。浮點數最好用來表示象測量值這類數值,這類值從一開始就不怎么精確。
所以一般對double類型進行運算時,做好對結果進行處理,然后拿這個值去做其他事情。 下面我們就用代碼來說明一下如何對浮點數進行精度計算,以double為例。
詳細代碼(注釋很詳細 不做解釋了) 可點擊這里下載代碼

/**//*
* Copyright reserved 2010 by AllensLab
* @project AllensLab
* @date Feb 22, 2010
*/
package cn.allen.tools;

import java.math.BigDecimal;


/** *//**
* RoundTool
* @author allen
* @time 10:05:40 AM Feb 22, 2010
*/

public class RoundTools
{

/** *//**
* 對double數據進行取精度.
* <p>
* For example: <br>
* double value = 100.345678; <br>
* double ret = round(value,4,BigDecimal.ROUND_HALF_UP); <br>
* ret為100.3457 <br>
*
* @param value
* double數據.
* @param scale
* 精度位數(保留的小數位數).
* @param roundingMode
* 精度取值方式.
* @return 精度計算后的數據.
*/

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;
}

public static BigDecimal roundAgain(double value, int scale, int roundingMode)
{
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(scale, roundingMode);
return bd;
}

/**//* 用于加、減、乘和除的方法給 BigDecimal 值提供了算術運算。由于 BigDecimal 對象是不可變的,這些方法中的每一個都會產生新的 BigDecimal 對象。
* 因為創建對象的開銷, BigDecimal 不適合于大量的數學計算,但設計它的目的是用來精確地表示小數。 */

/** *//**
* Addition
* @param number1
* @param number2
* @return
* @author allen
* @date 10:42:47 AM Feb 22, 2010
*/

public static double add(double number1, double number2, int newScale, int roundingMode)
{
return BigDecimal.valueOf(number1).add(BigDecimal.valueOf(number2)).setScale(newScale, roundingMode).doubleValue();
}

public static double add(int newScale, int roundingMode, double number1, double
numbers)
{
BigDecimal bd = new BigDecimal(number1);

for (double number : numbers)
{
bd = bd.add(BigDecimal.valueOf(number).setScale(newScale, roundingMode));
}
return bd.doubleValue();
}

/** *//**
* Subtraction
* @param number1
* @param number2
* @return
* @author allen
* @date 10:45:36 AM Feb 22, 2010
*/

public static double subtract(double number1, double number2, int newScale, int roundingMode)
{
return BigDecimal.valueOf(number1).subtract(BigDecimal.valueOf(number2)).setScale(newScale, roundingMode).doubleValue();
}

/** *//**
* Multiplication
* @param number1
* @param number2
* @return
* @author allen
* @date 10:46:23 AM Feb 22, 2010
*/

public static double multiply(double number1, double number2, int newScale, int roundingMode)
{
return BigDecimal.valueOf(number1).multiply(BigDecimal.valueOf(number2)).setScale(newScale, roundingMode).doubleValue();
}

/** *//**
* Division
* 盡量采用財務常用的四舍六入五取偶 即ROUND_HALF_EVEN
* @param number1
* @param number2
* @return
* @author allen
* @date 10:47:12 AM Feb 22, 2010
*/

public static double divide(double number1, double number2, int scale, int roundingMode)
{
return BigDecimal.valueOf(number1).divide(BigDecimal.valueOf(number2), scale, roundingMode).doubleValue();
}

/** *//**
* 測試用的main方法.
*
* @param args
* 運行參數.
*/

public static void main(String[] args)
{
//下面都以保留2位小數為例
System.out.println(add(12.341, 12.3449, 2, BigDecimal.ROUND_HALF_EVEN));
System.out.println(add(2, BigDecimal.ROUND_HALF_UP, 12.346, 12.3449, 12.3401, 12.345));
System.out.println(subtract(12.3449, 12.341, 2, BigDecimal.ROUND_HALF_EVEN));
System.out.println(multiply(12.3449, 0.01, 2, BigDecimal.ROUND_HALF_UP));
System.out.println(divide(11.341, 12.346, 2, BigDecimal.ROUND_HALF_EVEN));
//ROUND_UP
//只要第2位后面存在大于0的小數,則第2位就+1
System.out.println("-- ROUND_UP -- 只要第2位后面存在大于0的小數,則第2位就+1 --");
System.out.println(round(12.3401, 2, BigDecimal.ROUND_UP));//12.35
System.out.println(round(-12.3401, 2, BigDecimal.ROUND_UP));//-12.35
//ROUND_DOWN
//與ROUND_UP相反
//直接舍棄第2位后面的所有小數
System.out.println("-- ROUND_DOWN -- 直接舍棄第2位后面的所有小數 --");
System.out.println(round(12.349, 2, BigDecimal.ROUND_DOWN));//12.34
System.out.println(round(-12.349, 2, BigDecimal.ROUND_DOWN));//-12.34
//ROUND_CEILING
//如果數字>0 則和ROUND_UP作用一樣
//如果數字<0 則和ROUND_DOWN作用一樣
System.out.println("-- OUND_CEILING -- 如果數字>0 則和ROUND_UP作用一樣 如果數字<0 則和ROUND_DOWN作用一樣 --");
System.out.println(round(12.3401, 2, BigDecimal.ROUND_CEILING));//12.35
System.out.println(round(-12.349, 2, BigDecimal.ROUND_CEILING));//-12.34
//ROUND_FLOOR
//如果數字>0 則和ROUND_DOWN作用一樣
//如果數字<0 則和ROUND_UP作用一樣
System.out.println("-- ROUND_FLOOR -- 如果數字>0 則和ROUND_DOWN作用一樣 如果數字<0 則和ROUND_UP作用一樣 --");
System.out.println(round(12.349, 2, BigDecimal.ROUND_FLOOR));//12.34
System.out.println(round(-12.3401, 2, BigDecimal.ROUND_FLOOR));//-12.35
//ROUND_HALF_UP [這種方法最常用]
//如果第3位數字>=5,則第2位數字+1
//備注:只看第3位數字的值,不會考慮第3位之后的小數的
System.out.println("-- ROUND_HALF_UP -- 如果第3位數字>=5,則第2位數字+1 --");
System.out.println(round(12.345, 2, BigDecimal.ROUND_HALF_UP));//12.35
System.out.println(round(12.3449, 2, BigDecimal.ROUND_HALF_UP));//12.34
System.out.println(round(-12.345, 2, BigDecimal.ROUND_HALF_UP));//-12.35
System.out.println(round(-12.3449, 2, BigDecimal.ROUND_HALF_UP));//-12.34
//ROUND_HALF_DOWN
//如果第3位數字>=5,則做ROUND_UP
//如果第3位數字<5,則做ROUND_DOWN
System.out.println("-- ROUND_HALF_DOWN -- 如果第3位數字>=5,則做ROUND_UP,如果第3位數字<5,則做ROUND_DOWN --");
System.out.println(round(12.345, 2, BigDecimal.ROUND_HALF_DOWN));//12.35
System.out.println(round(12.3449, 2, BigDecimal.ROUND_HALF_DOWN));//12.34
System.out.println(round(-12.345, 2, BigDecimal.ROUND_HALF_DOWN));//-12.35
System.out.println(round(-12.3449, 2, BigDecimal.ROUND_HALF_DOWN));//-12.34
//ROUND_HALF_EVEN
//如果第3位是偶數,則做ROUND_HALF_DOWN
//如果第3位是奇數,則做ROUND_HALF_UP
System.out.println("-- ROUND_HALF_EVEN -- 如果第3位是偶數,則做ROUND_HALF_DOWN,如果第3位是奇數, 則做ROUND_HALF_UP --");
System.out.println(round(12.346, 2, BigDecimal.ROUND_HALF_EVEN));//12.35
System.out.println(round(12.345, 2, BigDecimal.ROUND_HALF_EVEN));//12.35
}
}

再說說BigDecimal
用于較小數的 BigDecimal
從 JDK 1.3 起,Java 開發人員就有了另一種數值表示法來表示非整數: BigDecimal
。 BigDecimal
是標準的類,在編譯器中不需要特殊支持,它可以表示任意精度的小數,并對它們進行計算。在內部,可以用任意精度任何范圍的值和一個換算因子來表示 BigDecimal
,換算因子表示左移小數點多少位,從而得到所期望范圍內的值。因此,用 BigDecimal
表示的數的形式為 unscaledValue*10 -scale
。
用于加、減、乘和除的方法給 BigDecimal
值提供了算術運算。由于 BigDecimal
對象是不可變的,這些方法中的每一個都會產生新的 BigDecimal
對象。因此,因為創建對象的開銷, BigDecimal
不適合于大量的數學計算,但設計它的目的是用來精確地表示小數。如果您正在尋找一種能精確表示如貨幣量這樣的數值,則 BigDecimal
可以很好地勝任該任務。
所有的 equals 方法都不能真正測試相等
如浮點類型一樣, BigDecimal
也有一些令人奇怪的行為。尤其在使用 equals()
方法來檢測數值之間是否相等時要小心。 equals()
方法認為,兩個表示同一個數但換算值不同(例如, 100.00
和 100.000
)的 BigDecimal
值是不相等的。然而, compareTo()
方法會認為這兩個數是相等的,所以在從數值上比較兩個 BigDecimal
值時,應該使用 compareTo()
而不是 equals()
。
另外還有一些情形,任意精度的小數運算仍不能表示精確結果。例如, 1
除以 9
會產生無限循環的小數 .111111...
。出于這個原因,在進行除法運算時, BigDecimal
可以讓您顯式地控制舍入。 movePointLeft()
方法支持 10 的冪次方的精確除法。
使用 BigDecimal 作為互換類型
SQL-92 包括 DECIMAL
數據類型,它是用于表示定點小數的精確數字類型,它可以對小數進行基本的算術運算。一些 SQL 語言喜歡稱此類型為 NUMERIC
類型,其它一些 SQL 語言則引入了 MONEY
數據類型,MONEY 數據類型被定義為小數點右側帶有兩位的小數。
如果希望將數字存儲到數據庫中的 DECIMAL
字段,或從 DECIMAL
字段檢索值,則如何確保精確地轉換該數字?您可能不希望使用由 JDBC PreparedStatement
和 ResultSet
類所提供的 setFloat()
和 getFloat()
方法,因為浮點數與小數之間的轉換可能會喪失精確性。相反,請使用 PreparedStatement
和 ResultSet
的 setBigDecimal()
及 getBigDecimal()
方法。
對于 BigDecimal
,有幾個可用的構造函數。其中一個構造函數以雙精度浮點數作為輸入,另一個以整數和換算因子作為輸入,還有一個以小數的 String
表示作為輸入。要小心使用 BigDecimal(double)
構造函數,因為如果不了解它,會在計算過程中產生舍入誤差。請使用基于整數或 String
的構造函數。
構造 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
來避免這類問題,因為這兩種方法都可以精確地表示小數。
本文參考了一下文章,對這些作者表示感謝!
THE END
posted on 2010-02-22 13:59
小立飛刀 閱讀(5509)
評論(0) 編輯 收藏 所屬分類:
Others