這篇文章我們會介紹一下Java 中的BigDecimal,并且會通過一些例子示範它的用法,例如精度的操作
Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數進行精确的運算。雙精度浮點型變量double可以處理16位有效數,但在實際應用中,可能需要對更大或者更小的數進行運算和處理。一般情況下,對于那些不需要準确計算精度的數字,我們可以直接使用Float和Double處理,但是Double.valueOf(String) 和Float.valueOf(String)會丢失精度。是以開發中,如果我們需要精确計算的結果,則必須使用BigDecimal類來操作。
前面學習基本類型的時候,我們可以使用float 和 double來表示浮點型數字,但是這裡有一個問題那就是基本資料類型float 和 double不應該用于高精度資料的表示,例如貨币,因為浮點類型的float 和 double會丢失資料的精度
這裡本應輸出的是0.30,但是實際上輸出如下
這就是為什麼在金融相關的程式裡資料類型如此重要的原因,是以我們更好的選擇是BigDecimal 而不是float 和 double
BigDecimal 是不可變的,任意精度的有符号十進制類型的數字,可用于貨币計算
上面那個例子如果我們是使用BigDecimal來代替double 我們可以獲得準确的值
現在輸出就和預期的一樣了
Java 中的BigDecimal類繼承自Number并且實作了Comparable接口
BigDecimal 提供了很多的構造方法,可以使用int ,char[],BigDecimal,String,doble ,long,int來初始化BigDecimal,BigDecimal 總共提供了18種構造方法,需要注意的實如果使用double 來初始化BigDecimal或許會再次引入精度的問題,下面提供了一個例子
輸出結果是這樣的
Thus it is always safe to go with a constructor that takes String as argument when representing a decimal value.
是以使用String 做為構造函數的參數來表示一個十進制的數字的時候總是安全的
Output
使用BigDecimal的一個理由是BigDecimal提供了精度控制(小數點後的數字的多少)和舍入模式,為了确定小數點後的保留幾位數字你可以使用setScale(int scale) 方法,但是最好的是在使用精度的時候提供舍入模式,也就是setScale的重載方法
setScale(int newScale, int roundingMode)
setScale(int newScale, RoundingMode roundingMode)
接下來我們通過一個例子示範一下我們為什麼要這樣做,假設我們在通過一個double值構造一個BigDecimal
從上面的輸出中我們看到進度已經丢失了,輸出的BigDecimal 是23.120000000000000994759830064140260219573974609375
并且我們看到當我們将精度設定為1 的時候并且沒有提供舍入機制的時候導緻Arithmetic異常被抛出
如果你注意到了上面我們在講精度設定的時候,它其實是有兩個設定精度的重載方法,第二個參數代表的就是舍入模式模式的參數,BigDecimal提供了八種舍入模式,它們通過static final int 進行表示
需要注意的是在java.math包中也提供舍入模式的枚舉值,需要注意的我們是推薦使用枚舉值來代替使用int 類型的常量做舍入摸模式的參數
下面我們在設定進度的同時設定一下舍入模式,來避免Arithmetic異常
但是我們說了,我們推薦使用枚舉值的舍入模式,而不是直接使用int 類型的常量,接下來我們看一下<code>RoundingMode</code> 提供的枚舉值
CEILING- Rounding mode to round towards positive infinity.
DOWN- Rounding mode to round towards zero.
FLOOR- Rounding mode to round towards negative infinity.
HALF_DOWN- Rounding mode to round towards “nearest neighbor” unless both neighbors are equidistant, in which case round down.
HALF_EVEN- Rounding mode to round towards the “nearest neighbor” unless both neighbors are equidistant, in which case, round towards the even neighbor.
HALF_UP- Rounding mode to round towards “nearest neighbor” unless both neighbors are equidistant, in which case round up.
UNNECESSARY - Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.
UP- Rounding mode to round away from zero.
下面我們通過例子來總結一下這些舍入模式的舍入方式
Result of rounding input to one digit with the given rounding mode
Input Number
<code>UP</code>
<code>DOWN</code>
<code>CEILING</code>
<code>FLOOR</code>
<code>HALF_UP</code>
<code>HALF_DOWN</code>
<code>HALF_EVEN</code>
<code>UNNECESSARY</code>
5.5
6
5
throw <code>ArithmeticException</code>
2.5
3
2
1.6
1
1.1
-1.0
-1.1
-2
-1.6
-2.5
-3
-5.5
-6
-5
更多細節參考- https://docs.oracle.com/javase/8/docs/api/java/math/RoundingMode.html
由于NumberFormat類的format()方法可以使用BigDecimal對象作為其參數,可以利用BigDecimal對超出16位有效數字的貨币值,百分值,以及一般數值進行格式化控制。
以利用BigDecimal對貨币和百分比格式化為例。首先,建立BigDecimal對象,進行BigDecimal的算術運算後,分别建立對貨币和百分比格式化的引用,最後利用BigDecimal對象作為format()方法的參數,輸出其格式化的貨币值和百分比。
輸出結果
BigDecimal格式化保留2為小數,不足則補0
最常見的例子就是精度是2(小數點後保留兩位),并且采用四舍五入的舍入模式(如果指定精度的笑一個數字大于等于5則向上取整,否則向下取整)
因為精度設定為2之後,也就是小數點後兩位的後一位數字是6大于等于5,是以向上取整,是以結果是<code>23.13</code>
因為精度設定為2之後,也就是小數點後兩位的後一位數字是3小于5,是以向下取整,是以結果是<code>23.12</code>
對于負數,也是同樣的道理,是以輸出是<code>bd1 -15.57</code>
在Java 中支援的(+, -, *, /)數學運算,BigDecimal并不支援,因為這些操作符是針對基本資料類型的,但是BigDecimal是引用類型,也就是基于對象和類的,是以BigDecimal提供了下面的方法
<code>add,subtract,multiply,and,divide</code>
例如乘法在BigDecimal的實作如下
需要注意如果你使用 equals()來比較兩個BigDecimal數字,那隻有當兩個BigDecimal的值和精度都相同的時候equals()猜認為它們是相同的(是以2.0和2.00是不相同的)
是以你應該使用compareTo()方法來比較兩個BigDecimal 是否是相等的,BigDecimal實作了comparable接口并且提供了自己的compareTo方法,這個方法隻會判斷兩個BigDecimal對象的值是否是相等的忽略了兩個數字的精度(ike 2.0 和 2.00 相等的)
對于bd1.compareTo(bd2) 的傳回值
-1 bd1 小于 bd2.
0 兩個相等的
1 bd1 大于 bd2.
BigDecimal 對象是不可變的,是以是線程安全的,在進行每一次四則運算時,都會産生一個新的對象 ,是以在做加減乘除運算時要記得要儲存操作後的值。
當我們在進行有着高精度的計算要求的時候不要使用double和float 因為它們有着精度丢失的問題
如果使用BigDecimal的時候,不要選擇double值作為初始化的值,因為它同樣會引入精度的問題
如果你使用BigDecimal時候設定了精度,那就同時提供舍入模式,告訴BigDecimal如何舍入進而提供你想要的精度
BigDecimal繼承了Number類和實作了Comparable接口
BigDecimal 針對加減乘除提供可特定的方法,因為BigDecimal不支援(+, -, *, /)數學運算
BigDecimal 的對象是不可變的
BigDecimal因為建立對象開銷的原因,是以很多操作都是比原生類型要慢一些的。