今天羣裏一個初級開發者問爲何測試人員測出來他寫的價格計算模塊有計算誤差的問題,他檢查了半天也沒找出問題。這裏小胖哥要提醒你,商業計算請務必使用BigDecimal
,浮點作商業運算是不精確的。由於計算機沒法使用二進制小數來精確描述咱們程序中的十進制小數。《Effective Java》在第48條也推薦「使用BigDecimal來作精確運算」。今天咱們就來總結概括其相關的知識點。java
BigDecimal表示不可變的任意精度帶符號十進制數。它由兩部分組成:git
BigInteger
例如,BigDecimal 3.14的未校訂值爲314,縮放爲2。咱們使用BigDecimal進行高精度算術運算。咱們還將它用於須要控制比例和舍入行爲的計算。若是你的計算是商業計算請務必使用計算精確的BigDecimal
。api
咱們能夠從String
,character
數組,int
,long
和BigInteger
建立一個BigDecimal
對象:數組
@Test public void theValueMatches() { BigDecimal bdFromString = new BigDecimal("0.12"); BigDecimal bdFromCharArray = new BigDecimal(new char[]{'3', '.', '1', '4', '1', '5'}); BigDecimal bdlFromInt = new BigDecimal(42); BigDecimal bdFromLong = new BigDecimal(123412345678901L); BigInteger bigInteger = BigInteger.probablePrime(100, new Random()); BigDecimal bdFromBigInteger = new BigDecimal(bigInteger); assertEquals("0.12", bdFromString.toString()); assertEquals("3.1415", bdFromCharArray.toString()); assertEquals("42", bdlFromInt.toString()); assertEquals("123412345678901", bdFromLong.toString()); assertEquals(bigInteger.toString(), bdFromBigInteger.toString()); }
咱們還能夠從double
建立BigDecimal
:dom
@Test public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() { BigDecimal bdFromDouble = new BigDecimal(0.1d); assertNotEquals("0.1", bdFromDouble.toString()); }
咱們發如今這種狀況下,結果與預期的結果不一樣(即0.1)。這是由於:這個轉換結果是double
的二進制浮點值的精確十進制表示,其值得結果不是咱們能夠預測的.咱們應該使用String
構造函數而不是double
構造函數。另外,咱們可使用valueOf
靜態方法將double
轉換爲BigDecimal
或者直接使用其未校訂數加小數位數 :ide
@Test public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() { BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d); BigDecimal bigFromLong=BigDecimal.valueOf(1,1); assertEquals("0.1", bdFromDouble.toString()); assertEquals("0.1", bigFromLong.toString()); }
在轉換爲BigDecimal以前,此方法將double轉換爲其String表示形式。此外,它能夠重用對象實例。所以,咱們應該優先使用valueOf方法來構造函數。函數
方法名 | 對應方法相關用法解釋 |
---|---|
abs() | 絕對值,scale不變 |
add(BigDecimal augend) | 加,scale爲augend和原值scale的較大值 |
subtract(BigDecimal augend) | 減,scale爲augend和原值scale的較大值 |
multiply(BigDecimal multiplicand) | 乘,scale爲augend和原值scale的和 |
divide(BigDecimal divisor) | 除,原值/divisor,若是不能除盡會拋出異常,scale與原值一致 |
divide(BigDecimal divisor, int roundingMode) | 除,指定舍入方式,scale與原值一致 |
divide(BigDecimal divisor, int scale, int roundingMode) | 除,指定舍入方式和scale |
remainder(BigDecimal divisor) | 取餘,scale與原值一致 |
divideAndRemainder(BigDecimal divisor) | 除法運算後返回一個數組存放除盡和餘數 如 23/3 返回 {7,2} |
divideToIntegralValue(BigDecimal divisor) | 除,只保留整數部分,但scale仍與原值一致 |
max(BigDecimal val) | 較大值,返回原值與val中的較大值,與結果的scale一致 |
min(BigDecimal val) | 較小值,與結果的scale一致 |
movePointLeft(int n) | 小數點左移,scale爲原值scale+n |
movePointRight(int n) | 小數點右移,scale爲原值scale+n |
negate() | 取反,scale不變 |
pow(int n) | 冪,原值^n,原值的n次冪 |
scaleByPowerOfTen(int n) | 至關於小數點右移n位,原值*10^n |
BigDecimal上的操做就像其餘Number類(Integer,Long,Double等)同樣,BigDecimal提供算術和比較操做的操做。它還提供了縮放操做,舍入和格式轉換的操做。它不會使算術運算符+, - ,/,*
或邏輯運算符>、< 、|、&
過載。相反,咱們使用BigDecimal
相應的方法 - 加,減,乘,除和比較。而且BigDecimal
具備提取各類屬性的方法。測試
精度,小數位數和符號:spa
@Test public void whenGettingAttributes_thenExpectedResult() { BigDecimal bd = new BigDecimal("-12345.6789"); assertEquals(9, bd.precision()); assertEquals(4, bd.scale()); assertEquals(-1, bd.signum()); }
咱們使用compareTo
方法比較兩個BigDecimal
的值:代理
@Test public void whenComparingBigDecimals_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); BigDecimal bd3 = new BigDecimal("2.0"); assertTrue(bd1.compareTo(bd3) < 0); assertTrue(bd3.compareTo(bd1) > 0); assertTrue(bd1.compareTo(bd2) == 0); assertTrue(bd1.compareTo(bd3) <= 0); assertTrue(bd1.compareTo(bd2) >= 0); assertTrue(bd1.compareTo(bd3) != 0); }
上面的方法在比較時忽略了小數位。若是你既要比較精度又要比較小數位數那麼請使用equals
方法:
@Test public void whenEqualsCalled_thenSizeAndScaleMatched() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); assertFalse(bd1.equals(bd2)); }
BigDecimal 提供瞭如下四則運算的方法:
ArithmeticException
異常@Test public void whenPerformingArithmetic_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("4.0"); BigDecimal bd2 = new BigDecimal("2.0"); BigDecimal sum = bd1.add(bd2); BigDecimal difference = bd1.subtract(bd2); BigDecimal quotient = bd1.divide(bd2); BigDecimal product = bd1.multiply(bd2); assertTrue(sum.compareTo(new BigDecimal("6.0")) == 0); assertTrue(difference.compareTo(new BigDecimal("2.0")) == 0); assertTrue(quotient.compareTo(new BigDecimal("2.0")) == 0); assertTrue(product.compareTo(new BigDecimal("8.0")) == 0); }
既然是數學運算就不得不講四捨五入。好比咱們在金額計算中很容易遇到最終結算金額爲人民幣22.355
的狀況。由於貨幣沒有比分更低的單位因此咱們要使用精度和舍入模式規則對數字進行剪裁。java提供有兩個類控制舍入行爲RoundingMode
和MathContext
。MathContext
執行的是IEEE 754R標準目前不太明白其使用場景,咱們使用的比較多的是枚舉RoundingMode
。它提供了八種模式:
數字格式化可經過操做類java.text.NumberFormat
和java.text.DecimalFormat
提供的api進行操做。其實咱們只須要使用java.text.DecimalFormat
,由於它代理了NumberFormat
。咱們來看一下它們的api:
DecimalFormat
除了能代理上面的NumberFormat
之外,還提供了基於pattern
字符串的格式化風格,有點相似格式化時間同樣。咱們來看看pattern
的規則:
今天對BigDecimal
進行了總結概括,這篇文章建議你收藏備用,也能夠轉給其餘須要的同窗。
關注公衆號:碼農小胖哥 獲取更多資訊