Java开发笔记(三十)大小数BigDecimal

Wesley13
• 阅读 551

前面介绍的BigInteger只能表达任意整数,但不能表达小数,要想表达任意小数,还需专门的大小数类型BigDecimal。如果说设计BigInteger的目的是替代int和long类型,那么设计BigDecimal的目的便是替代浮点型float和双精度型double了。正如它的兄弟BigInteger一般,BigDecimal不存在什么数值范围限制,无论是整数部分还是小数部分,只要你能写得出来,BigDecimal就能表达出来,从此不必担心基本数字类型的精度问题了。
既然同为大数字家族,BigDecimal的绝大部分用法就与BigInteger保持一致,像add方法、subtract方法、abs方法、pow方法等等直接拿来便是,这里不再重复啰嗦了,且看下面BigDecimal的方法调用代码:

// 生成一个指定数值的大小数变量
        BigDecimal sevenAndHalf = BigDecimal.valueOf(7.5);
        BigDecimal three = BigDecimal.valueOf(3);
        // add方法用来替代加法运算符“+”
        BigDecimal sum = sevenAndHalf.add(three);
        System.out.println("sum="+sum);
        // subtract方法用来替代减法运算符“-”
        BigDecimal sub = sevenAndHalf.subtract(three);
        System.out.println("sub="+sub);
        // multiply方法用来替代乘法运算符“*”
        BigDecimal mul = sevenAndHalf.multiply(three);
        System.out.println("mul="+mul);
        // divide方法用来替代除法运算符“/”
        BigDecimal div = sevenAndHalf.divide(three);
        System.out.println("div="+div);
        // remainder方法用来替代取余数运算符“%”
        BigDecimal remainder = sevenAndHalf.remainder(three);
        System.out.println("remainder="+remainder);
        // negate方法用来替代负号运算符“-”
        BigDecimal neg = sevenAndHalf.negate();
        System.out.println("neg="+neg);
        // abs方法用来替代数学库函数Math.abs
        BigDecimal abs = sevenAndHalf.abs();
        System.out.println("abs="+abs);
        // pow方法用来替代数学库函数Math.pow
        BigDecimal pow = sevenAndHalf.pow(2);
        System.out.println("pow="+pow);

哇噻,难道这么容易就学会使用BigDecimal了吗?仔细看上面的例子代码,被除数是7.5,除数是3,二者相除得到的商为2.5。注意这是除得尽的情况,倘若换个除不尽的情况,例如把除数改成7,7.5除以7结果理应得到一个无限循环小数。可要是运行以下的测试代码,没想到程序竟然运行异常,未能打印那个值为无限循环小数的商。

// 只有一个输入参数的divide方法,要求被除数能够被除数除得尽。
        // 倘若除不尽,也就是商为无限循环小数,则程序会异常退出,
        // 报错“Non-terminating decimal expansion; no exact representable decimal result.”
        BigDecimal seven = BigDecimal.valueOf(7);
        BigDecimal divTest = sevenAndHalf.divide(seven);
        System.out.println("divTest="+divTest);

虽说大小数能够表示任意范围的小数,但必须是个有限的范围,而不能是无限的范围。由于内存容量是有限的,一个无限循环小数写出来都写不完,要是放到内存就需要无限大小的内存,因此为了让内存能够放得下无限循环小数,只好给该小数指定需要保留的小数位数,也就意味着BigDecimal表示无限循环小数时还是有精度要求的。
除了规定小数部分的保留位数,还需明确多余部分的数字是直接舍弃还是四舍五入?这样对于无限循环小数来说,除法运算的divide方法需要三个输入参数,包括除数、需要保留的小数位数、多余数字的舍入规则。BigDecimal提供的数字舍入规则主要有下列几种:
ROUND_CEILING:往数值较小的方向取整,类似于Math库的ceiling函数。
ROUND_FLOOR:往数值较大的方向取整,类似于Math库的floor函数。
ROUND_HALF_UP:四舍五入取整,若多余的数字等于.5,则前一位进1,类似于Math库的round函数。
ROUND_HALF_DOWN:类似四舍五入取整,区别在于:若多余的数字等于.5,则直接舍弃。
ROUND_HALF_EVEN:如果保留位数的末尾为奇数,则按照ROUND_HALF_UP方式取整。如果保留位数的末尾为偶数,则按照ROUND_HALF_DOWN方式取整。
由上述规则可知,通常情况下的四舍五入应当采取ROUND_HALF_UP方式。于是重新指定了小数精度和舍入规则,改写后大小数的除法运算代码示例如下:

BigDecimal one = BigDecimal.valueOf(100);
        BigDecimal three = BigDecimal.valueOf(3);
        // 大小数的除法运算,小数点后面保留64位,其中最后一位做四舍五入
        BigDecimal div = one.divide(three, 64, BigDecimal.ROUND_HALF_UP);
        System.out.println("div="+div);

运行修改后的除法代码,控制台打印的日志结果见下:

div=33.3333333333333333333333333333333333333333333333333333333333333333

可见此时除法计算正常工作,并且结果值的小数部分确实保留到了64位。
上述带三个输入参数的divide方法固然实现了符合精度的除法运算,但若代码存在多处调用divide方法,便意味着该方法后面的精度规则“64, BigDecimal.ROUND_HALF_UP”在每处调用的地方都会出现,这样不但造成代码重复,而且要是变更精度规则还得改动多处。为此Java又提供了工具MathContext,利用该工具可事先指定包含小数精度和舍入规则在内的精度规则,然后把设置好的工具对象传给divide方法就好了。下面是使用MathContext工具辅助除法运算的代码例子:

// 利用工具MathContext,可以把divide方法的输入参数减少为两个
        MathContext mc = new MathContext(64, RoundingMode.HALF_UP);
        BigDecimal divByMC = one.divide(three, mc);
        System.out.println("divByMC="+divByMC);

在大小数的除法中引入精度工具MathContext,至少有两个好处,其一为:只要定义一次,即可多处使用;其二为:若要变更精度规则,只需修改一个地方。

更多Java技术文章参见《Java开发笔记(序)章节目录

点赞
收藏
评论区
推荐文章
Java中的浮点数四舍五入到小数点后2位的几种方法
前言四舍五入到2或3个小数位是我们Java程序员日常开发中肯定会遇到。幸运的是,JavaAPI提供了几种在Java中舍入数字的方法我们可以使用Math.round(),BigDecimal或DecimalFormat将Java中的任何浮点数四舍五入到n个位置。我个人更喜欢使用BigDecimal在Java中四舍五入任何数字,因为它具有便捷的API并
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
java浮点型精度丢失浅析
java浮点型数值在运算中会出现精度损失的情况,在业务要求比较高比如交易等场景,一般使用BigDecimal来解决精度丢失的情况。最近一个同事在使用BigDecimal时仍然出现了精度损失,简略记录一下测试用例代码如下@Testpublicvoidfd(){doubleabc
Wesley13 Wesley13
2年前
java.math.BigDecimal类的用法
在java中提供了大数字的操作类,即java.math.BinInteger类和java.math.BigDecimal类。这两个类用于高精度计算,其中BigInteger类是针对大整数的处理类,而BigDecimal类则是针对大小数的处理类。下边我们介绍BigDecimal类:BigDecimal的实现利用到了BigInteger,不同的是Big
Wesley13 Wesley13
2年前
Java:利用BigDecimal类巧妙处理Double类型精度丢失
本篇要点简单描述浮点数十进制转二进制精度丢失的原因。介绍几种创建BigDecimal方式的区别。整理了高精度计算的工具类。学习了阿里巴巴Java开发手册关于BigDecimal比较相等的规定。经典问题:浮点数精度丢失精度丢失的问题是在其他计算机语言中也都会出
Wesley13 Wesley13
2年前
Java中BigDecimal的8种舍入模式
java.math.BigDecimal不可变的、任意精度的有符号十进制数。BigDecimal由任意精度的整数非标度值和32位的整数标度(scale)组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以10的负scale次幂。因此,BigDecimal表示的数值是(unsc
Stella981 Stella981
2年前
BigDecimal与Long、int之间的相互转换
在实际开发过程中BigDecimal是一个经常用到的数据类型,它和int、Long之间可以相互转换。转换关系如下代码展示:一、int转换成BigDecimal数据类型//int转换成bigDecimal类型publicstaticBigDecimalintToBigDecim
Wesley13 Wesley13
2年前
JDK8 BigDecimal API
第三篇先介绍以BigInteger为构造参数的构造器1publicBigDecimal(BigIntegerval){//根据BigInteger创建BigDecimal对象2scale0;//BigInteger为整数因此有效小数位数为03intVal
Stella981 Stella981
2年前
BigDecimal和BigInteger
在看《阿里巴巴开发手册》里面提到浮点数之间的等值判断不要用,而是指定误差范围或用BigDecimal,然后才记忆起备忘录里BigDecimal还没写呢,就这篇幅写了一星期,因为实习完全没有时间啊啊啊啊啊啊啊啊1\.BigDecimal介绍背景我们知道计算机
Wesley13 Wesley13
2年前
2.python内置数据结构
第一类:数值类型一.数值型包括1.int(整型):python3里数字不管多大永远都是int长整型,且没有大小限制,受限于内存区域的大小类型转换:int(x)返回一个整数2.float(浮点型):有整数部分和小数部分组成。支持十进制和科学计数法表示。只有双精度型类型转换:float(x)返回一个浮点数3.c