如何使用BigDecimal实现Java开发商业计算

前言

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

BigDecimal

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

  • intVal - 未校正精度的整数,类型为BigInteger
  • Scale - 一个32位整数,表示小数点右边的位数

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

构造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方法来构造函数。

常用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

BigDecimal操作

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

提取属性

精度,小数位数和符号:

@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 提供了以下四则运算的方法:

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

四舍五入

既然是数学运算就不得不讲四舍五入。比如我们在金额计算中很容易遇到最终结算金额为人民币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 异常

格式化

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

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。

DecimalFormat

DecimalFormat除了能代理上面的NumberFormat以外,还提供了基于pattern字符串的格式化风格,有点类似格式化时间一样。我们来看看pattern的规则:

  • “0”——表示一位数值,如没有,显示0。如“0000.0000”,整数位或小数位>4,按实际输出,<4整数位前面补0小数位后面补0,凑足4位。
  • “#”——表示任意位数的整数。如没有,则不显示。在小数点位使用,只表示一位小数,超出部分四舍五入。如:“#”:无小数,小数部分四舍五入。“.#”:整数部分不变,一位小数,四舍五入。“.##”:整数部分不变,二位小数,四舍五入。
  • “.”——表示小数点。注意一个pattern中只能出现一次,超过一次将格式化异常。
  • “,”——与模式“0”一起使用,表示逗号。注意一定不能在小数点后用,否则格式化异常。

总结

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java Bigdecimal使用原理详解

    一般来说,一提到Java里面的商业计算,我们都知道不能用float和double,因为他们无法进行精确计算.但是Java的设计者给编程人员提供了一个很有用的类BigDecimal,他可以完善float和double类无法进行精确计算的缺憾. BigDecimal类位于java.maths类包下.首先我们来看下如何构造一个BigDecimal对象.它的构造函数很多,这里挑选最常用的两个来演示一下:一个就是BigDecimal(double val),另一个就是BigDecimal(String s

  • Java使用BigDecimal进行高精度计算的示例代码

    首先看如下代码示例: System.out.println(0.05 + 0.01); System.out.println(0.05 - 0.03); System.out.println(1.025 * 100); System.out.println(305.1 / 1000); 输出结果为: 0.060000000000000005 0.020000000000000004 102.49999999999999 0.30510000000000004 Java语言支持两种基本的浮点类型:

  • Java编程BigDecimal用法实例分享

    Java中提供了大数字(超过16位有效位)的操作类,即 java.math.BinInteger 类和 java.math.BigDecimal 类,用于高精度计算. 其中 BigInteger 类是针对大整数的处理类,而 BigDecimal 类则是针对大小数的处理类. BigDecimal 类的实现用到了 BigInteger类,不同的是 BigDecimal 加入了小数的概念. float和Double只能用来做科学计算或者是工程计算;在商业计算中,对数字精度要求较高,必须使用 BigIn

  • Java中BigDecimal精度和相等比较的坑

    为什么要有BigDecimal ,他是干什么的 float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的.然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合.但是,商业计算往往要求结果精确,这时候就要使用BigDecimal啦. 什么是BigDecimal BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成.如果为零或正数,则标度是小数点后的

  • Java中BigDecimal的加减乘除、比较大小与使用注意事项

    前言 借用<Effactive Java>这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算.他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的.然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合.但是,商业计算往往要求结果精确,在java 里面,int 的最大值是:2147483647,现在如果想用比这个数大怎么办?换句话说,就是数值较大,这时候就用到了BigDecimal ,关于BigDecimal 的介绍

  • Java中BigDecimal类与int、Integer使用总结

    前言 我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题.如下一段代码: System.out.println(0.05 + 0.01); System.out.println(1.0 - 0.42); System.out.println(4.015 * 100); System.out.println(123.3 / 100); 输出: 0.060000000000000005 0.5800000000000001 401.49999999999994 1.23299999999999

  • Java中BigDecimal类的使用详解

    不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度.Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算.BigDecimal所创建的是对象,我们不能使用传统的+.-.*./等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法.方法中的参数也必须是BigDecimal的对象.构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象. 一.BigDecimal转换取Double数据 假设我

  • Java BigDecimal和double示例及相关问题解析

    BigDecimal类 对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数的操作.BigDecimal类的常用方法如表1所示. 表1 BigDecimal类的常用方法 序号 方    法 类型 描    述 1 public BigDecimal(double val) 构造 将double表示形式转换 为BigDecimal 2 public BigDecimal(in

  • Java中BigDecimal的基本运算(详解)

    BigDecimal一共有4个够造方法,让来看看其中比较常用的两种用法: 第一种:BigDecimal(double val) Translates a double into a BigDecimal. 第二种:BigDecimal(String val) Translates the String repre sentation of a BigDecimal into a BigDecimal. 使用BigDecimal要用String来够造,要做一个加法运算,需要先将两个浮点数转为Str

  • 如何使用BigDecimal实现Java开发商业计算

    前言 今天群里一个初级开发者问为什么测试人员测出来他写的价格计算模块有计算偏差的问题,他检查了半天也没找出问题.这里小胖哥要提醒你,商业计算请务必使用BigDecimal,浮点做商业运算是不精确的.因为计算机无法使用二进制小数来精确描述我们程序中的十进制小数.<Effective Java>在第48条也推荐"使用BigDecimal来做精确运算".今天我们就来总结归纳其相关的知识点. BigDecimal BigDecimal表示不可变的任意精度带符号十进制数.它由两部分组

  • java开发使用BigDecimal避坑四则

    目录 引言 第一:浮点类型的坑 第二:浮点精度的坑 第三:设置精度的坑 第四:三种字符串输出的坑 小结 引言 在使用BigDecimal时,有4种使用场景下的坑,你一定要了解一下,如果使用不当,必定很惨.掌握这些案例,当别人写出有坑的代码,你也能够一眼识别出来,大牛就是这么练成的. 第一:浮点类型的坑 在学习了解BigDecimal的坑之前,先来说一个老生常谈的问题:如果使用Float.Double等浮点类型进行计算时,有可能得到的是一个近似值,而不是精确的值. 比如下面的代码: @Test p

  • [JAVA]十四种Java开发工具点评

    在计算机开发语言的历史中,从来没有哪种语言象Java那样受到如此众多厂商的支持,有如此多的开发工具,Java菜鸟们如初入大观园的刘姥姥,看花了眼,不知该何种选择.的确,这些工具各有所长,都没有绝对完美的,就算是老鸟也很难做出选择.在本文中我简要介绍了常见的十四种Java开发工具的特点,管中窥"器",希望能对大家有所帮助. 1.JDK (Java Development Kit) 2.Java Workshop 3.NetBeans 与Sun Java Studio 5 4.Borlan

  • Java开发常用类库之Hutool详解

    简介与安装 简介 Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以"甜甜的". Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当: Hutool是项目中"util"包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可

  • Java开发人员需知的十大戒律

    本文讲述了Java开发人员需知的十大戒律.分享给大家供大家参考,具体如下: 作为一个Java开发人员提高自己代码的质量,可维护性,是个恒久不变的话题,网上看到这篇文章,拿来自勉. 对Java开发者来说,有许多的标准和最佳实践.本文列举了每一个开发人员必须遵从的十大基本法则:如果有了可以遵从的规则而不遵从,那么将导致的是十分悲惨的结局. 1. 在你的代码里加入注释 每个人都知道这点,但不知何故忘记了遵守.算一算有多少次你"忘记"了添加注释?这是事实:注释对程序在功能上没有实质的贡献.但是

  • Java开发中的容器概念、分类与用法深入详解

    本文实例讲述了Java开发中的容器概念.分类与用法.分享给大家供大家参考,具体如下: 1.容器的概念 在Java当中,如果有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合,集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体 2.容器与数组的关系 之所以需要容器: ① 数组的长度难以扩充 ② 数组中数据的类型必须相同 容器与数组的区别与联系: ① 容器不是数组,不能通过下标的方式访问容器中的元素 ② 数组的所有功能通过Arraylist容器都可以实现,只是实现的方式不

  • 必须详细与全面的Java开发环境搭建图文教程

    在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必须阶段,只有开发环境搭建好了,方可进行开发,良好的开发环境搭建,为后续的开发工作带来极大便利. 对于大公司来说,软件开发环境搭建工作一般是由运维来做,然而,对于小公司来说,这个工作就交给开发人员来做了,如开发经理.不管这个工作是交给运维人员做,还是 交给开发人员做,能确定的是:做这件事的人,一定是个资深的人,如此,方可让开发环境稳定运行,从而为后续的开发提供便利. 现实中,只有极少部分开发人员接触服务器(能接触的人,基本都是开发组长及其以

  • 如何把VS Code打造成Java开发IDE

    近期,公司推行正版化,本人使用的是JetBrains教育版,是不允许进行商业开发的,因此开启了艰难的备用IDE选型之路.最终,我选定了轻量级的Visual Studio Code(以下简称VS Code). 各种IDE选型比较的过程就不赘述了,Eclipse.NetBeans.Srping Tools Suite等等,只能说青菜萝卜各有所爱. 插件淘沙 众所周知,VS Code是一款轻量级的通用编辑器,和Eclipse一样全靠海量的插件扩展,网上各类插件推荐文章又都是面向VS Code的最大ID

  • 利用java开发简易版扫雷游戏

    1.简介 学了几周的Java,闲来无事,写个乞丐版的扫雷,加强一下Java基础知识. 2.编写过程 编写这个游戏,一共经历了三个阶段,编写了三个版本的游戏代码. 第一版:完成了扫雷游戏的基本雏形,实现了游戏的基本功能,游戏运行在cmd黑窗口中,以字符绘制游戏界面,无图形化窗口,通过控制台输入字符完成游戏控制.代码放置在一个java文件中,代码的可读性以及可扩展性都比较差. 第二版:在第一版实现基本功能的基础之上,对游戏代码进行重构,根据各部分的功能创建多个类,增加代码注释,提高代码的可读性以及可

  • 教你怎么用Java开发扫雷游戏

    一.效果图        二.实现思路 1.界面上可以点开的各种实际都是按钮,创建9行9列的二维数组,然后根据这个数组来创建JButton. 2.对应创建二维数组data,用来存取数据,0表示周围无雷,-1表示当前是雷,其他数字表示周围雷的数量. 3.对应创建二维数组state,用来存取按钮状态,0未打开,1 打开  2旗子  3 未知(控制显示对应的图标) 4.设置雷:随机行数 i 和列数 j,根据随机到 i.j 从二维数组data中取出对应的元素值,若值不为-1(不是雷),则将此元素data

随机推荐