点击蓝色“程序猿DD”关注我

回复“资源”获取独家整理的学习资料!

作者 | 码农小胖哥

来源 | 公众号「码农小胖哥」

1.

前言

今天群里一个初级开发者问为什么测试人员测出来他写的价格计算模块有计算偏差的问题,他检查了半天也没找出问题。这里小胖哥要提醒你,商业计算请务必使用`BigDecimal`,浮点做商业运算是不精确的。因为计算机无法使用二进制小数来精确描述我们程序中的十进制小数。《Effective Java》在第48条也推荐“使用BigDecimal来做精确运算”。今天我们就来总结归纳其相关的知识点。

2.

BigDecimal

BigDecimal表示不可变的任意精度带符号十进制数。它由两部分组成:

  • intVal - 未校正精度的整数,类型为`BigInteger`

  • Scale - 一个32位整数,表示小数点右边的位数

例如,BigDecimal 3.14的未校正值为314,缩放为2。我们使用BigDecimal进行高精度算术运算。我们还将它用于需要控制比例和舍入行为的计算。如果你的计算是商业计算请务必使用计算精确的`BigDecimal` 。

3.

构造BigDecimal实例

我们可以从`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`:
@Test
public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() {BigDecimal bdFromDouble = new BigDecimal(0.1d);assertNotEquals("0.1", bdFromDouble.toString());
}

我们发现在这种情况下,结果与预期的结果不同(即0.1)。这是因为:这个转换结果是`double`的二进制浮点值的精确十进制表示,其值得结果不是我们可以预测的.我们应该使用`String`构造函数而不是`double`构造函数。另外,我们可以使用`valueOf`静态方法将`double`转换为`BigDecimal` 或者直接使用其未校正数加小数位数 :
@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方法来构造函数。

4.

常用API

方法名 对应方法相关用法解释
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

5.

BigDecimal操作

BigDecimal上的操作就像其他Number类(Integer,Long,Double等)一样,BigDecimal提供算术和比较操作的操作。它还提供了缩放操作,舍入和格式转换的操作。它不会使算术运算符(+ - /*)或逻辑运算符(> < | &) 过载。相反,我们使用`BigDecimal`相应的方法 - 加,减,乘,除和比较。并且`BigDecimal`具有提取各种属性的方法。

5.1

提取属性

精度,小数位数和符号:
@Test
public void whenGettingAttributes_thenExpectedResult() {BigDecimal bd = new BigDecimal("-12345.6789");assertEquals(9, bd.precision());assertEquals(4, bd.scale());assertEquals(-1, bd.signum());
}

5.2

比较大小

我们使用`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));
}

5.3

四则运算

BigDecimal 提供了以下四则运算的方法:
  • add ——加法

  • subtract ——减法

  • divide ——除法,有可能除不尽,必须显式声明保留小数位数避免抛出`ArithmeticException`异常

  • multiply ——乘法

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

5.4

四舍五入

既然是数学运算就不得不讲四舍五入。比如我们在金额计算中很容易遇到最终结算金额为人民币`22.355`的情况。因为货币没有比分更低的单位所以我们要使用精度和舍入模式规则对数字进行剪裁。java提供有两个类控制舍入行为`RoundingMode`和`MathContext` 。`MathContext`执行的是IEEE 754R标准目前不太明白其使用场景,我们使用的比较多的是枚举`RoundingMode`。它提供了八种模式:
  • RoundingMode.UP:以小数位为原点 是正数取右边,负数取左边

  • RoundingMode.DOWN:以小数位为原点 也就是正数取左边,负数取右边

  • RoundingMode.FLOOR:取左边最近的正数

  • RoundingMode.CEILING:取右边最近的整数

  • RoundingMode.HALF_DOWN:五舍六入,负数先取绝对值再五舍六入再负数

  • RoundingMode.HALF_UP:四舍五入,负数原理同上

  • RoundingMode.HALF_EVEN:这个比较绕,整数位若是奇数则四舍五入,若是偶数则五舍六入

  • RoundingMode.ROUND_UNNECESSARY:不需要取整,如果存在小数位,就抛ArithmeticException 异常

6.

格式化

数字格式化可通过操作类`java.text.NumberFormat`和`java.text.DecimalFormat`提供的api进行操作。其实我们只需要使用`java.text.DecimalFormat`,因为它代理了`NumberFormat`。我们来看一下它们的api:

6.1

NumberFormat

  • NumberFormat.getInstance(Locale)、getNumberInstance(Locale)。返回指定语言环境的通用数值格式。

  • NumberFormat.getCurrencyInstance(Locale)。返回指定语言环境的货币格式。

  • NumberFormat.getPercentInstance(Locale)。返回指定语言环境的百分比格式。

  • NumberFormat.getIntegerInstance(Locale)。返回指定语言环境的整数数值格式。

  • NumberFormat.setMinimumIntegerDigits(int)。设置数的整数部分所允许的最小位数。

  • NumberFormat.setMaximumIntegerDigits(int)。设置数的整数部分所允许的最大位数。

  • NumberFormat.setMinimumFractionDigits(int)。设置最少小数点位数,不足的位数以0补位,超出的话按实际位数输出。

  • NumberFormat.setMaximumFractionDigits(int)。设置最多保留小数位数,不足不补0。

6.2

DecimalFormat

`DecimalFormat`除了能代理上面的`NumberFormat`以外,还提供了基于`pattern`字符串的格式化风格,有点类似格式化时间一样。我们来看看`pattern`的规则:
  • “0”——表示一位数值,如没有,显示0。如“0000.0000”,整数位或小数位>4,按实际输出,<4整数位前面补0小数位后面补0,凑足4位。

  • “#”——表示任意位数的整数。如没有,则不显示。在小数点位使用,只表示一位小数,超出部分四舍五入。如:“#”:无小数,小数部分四舍五入。“.#”:整数部分不变,一位小数,四舍五入。“.##”:整数部分不变,二位小数,四舍五入。

  • “.”——表示小数点。注意一个pattern中只能出现一次,超过一次将格式化异常。

  • “,”——与模式“0”一起使用,表示逗号。注意一定不能在小数点后用,否则格式化异常。

7.

总结

今天对`BigDecimal`进行了总结归纳,这篇文章建议你收藏备用,也可以转给其他需要的同学。 

本文通过OpenWrite的免费Markdown转换工具发布

-END-

留言交流不过瘾

关注我,回复“加群”加入各种主题讨论群

这里有点“”点击领取!

Java 中商业运算必备的精确运算类:BigDecimal相关推荐

  1. java中小数的处理:高精度运算用bigDecimal类,精度保留方法,即舍入方式的指定

    java中小数的处理:高精度运算用bigDecimal类,精度保留方法,即舍入方式的指定 2016年05月11日 11:20:08 阅读数:6336 一. 计算机的小数计算一定范围内精确,超过范围只能 ...

  2. 深入理解计算机系统(2.8)---浮点数的舍入,Java中的舍入例子以及浮点数运算(重要)

    https://www.cnblogs.com/zuoxiaolong/p/computer12.html 前言 上一章我们简单介绍了IEEE浮点标准,本次我们主要讲解一下浮点运算舍入的问题,以及简单 ...

  3. java中商业数据计算时用到的类BigDecimal和DecimalFormat

    1.引言 借用<Effactive Java>这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确 ...

  4. java中常见的几个内置类

    –Java中常见的内置类: --Scanner类 --Math类 --Random类 --String类 –Math类: --作用:Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初 ...

  5. dateformat 返回类型_详解Java中格式化日期的DateFormat与SimpleDateFormat类

    DateFormat其本身是一个抽象类,SimpleDateFormat 类是DateFormat类的子类,一般情况下来讲DateFormat类很少会直接使用,而都使用SimpleDateFormat ...

  6. Android+Java中使用Aes对称加密的工具类与使用

    场景 Android+Java中使用RSA加密实现接口调用时的校验功能: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/11146 ...

  7. Java中,我自己定义的某个类,去实现某个接口,是否必须实现该接口的全部抽象方法呢?

    不一定,关键要看子类是否是抽象类. 如果子类是非抽象类,则必须实现接口中的所有方法:如果子类是抽象类,则可以不实现接口中的所有方法,因为抽象类中允许有抽象方法的存在! 一.抽象类定义 抽象类往往用来表 ...

  8. Java中String,StringBuffer都是final类

    Java中String和StringBuffer同时final类,它们的区别在哪里 ? final 表示这个类不能再被继承.String不可变是说String中用一个final 的char数组priv ...

  9. JAVA中J.U.C 包下并发类的应用

    文章目录 JUC包中的锁应用 Lock接口及ReentrantLock对象分析及应用? Condition接口对象分析与应用? ReadWriteLock接口及实现类分析与应用? StampedLoc ...

最新文章

  1. C/C++ 移位计算代替乘除运算
  2. C++中逗号操作符重载的分析
  3. 数学基础、机器学习经典算法、统计学习方法,这份机器学习在线手册来帮你...
  4. mysql 终止 存储过程
  5. adobe FMS(flash media server)错误解决小结
  6. Android中Json数据解析
  7. 美图:已累计净购买价值约1亿美元的加密货币
  8. ios签名软件_使用ios企业签名需要准备哪些?
  9. Armv6 Armv7
  10. 我的游戏测试面试过程
  11. Microsoft Edge浏览器打开就是360导航的问题解决方法
  12. 远程服务器 Linux 用cityscape训练DeepLabv3模型(Pytorch版)并用图像测试
  13. 无线路由器怎么改密码
  14. 在CentOS7上使用LXC管理容器
  15. hdu 5055(坑)
  16. 有效的回旋镖(2022-6-8)每日一练
  17. 我的Python学习之路(5)
  18. 示例:Linux应用程序遍历当前系统的PCI设备
  19. 智能ABC输入法的巧用
  20. 三菱mr服务器如何显示脉冲数,MR-JE-200A参数设置三菱MR-JE-200A操作手册(故障排除篇) - 广州正凌...

热门文章

  1. python3 numpy. ndarray 与 list 互转方法
  2. linux centos 查找命令 属于哪个安装包 所属软件包
  3. pycharm 调试错误 Connection to Python debugger failed: Socket operation on nonsocket: configureBlocking
  4. linux 内核驱动模块的编译及加载
  5. FPGA和NIOS2的关系
  6. PHP如何添加自带的扩展库
  7. 用 Windows API “GetAdaptersInfo” 获取 MAC 时遇到的问题
  8. windbg学习-------.expr和masm表达式
  9. Win32 API之Setlocale函数配置地域化信息函数
  10. VS2012编译调试WDM驱动(KdPrint无调试信息 debugview win7无调试信息)