今天群里一个初级开发者问为何测试人员测出来他写的价格计算模块有计算误差的问题,他检查了半天也没找出问题。这里小胖哥要提醒你,商业计算请务必使用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
进行了总结概括,这篇文章建议你收藏备用,也能够转给其余须要的同窗。
关注公众号:码农小胖哥 获取更多资讯