前言:BigDecimal的产生背景
在Java的8种基本类型中,我们知道double和float基本数据类型存在着精度缺失问题。我们先来看一个例子。
double augend = 1.0000001; double addend = 0.0000001; double sum = augend + addend; //sum: 1.0000002000000001 System.out.println("sum: " + sum);
可以看到sum并不是我们所想要的1.0000002,而是1.0000002000000001。虽然误差非常小,但是如果开发银行金融类产品时,我们就需要绝对精确的数据。试想下这样一个场景,在一个用户余额为99.95元时,恰好他要购买总价99.95元的商品,而程序中定义总价时使用了double类型,这就有可能使得总价大于余额,导致用户无法购买商品。不过,Java提供了BigDecimal类用来了解决此类问题。
一. 如何使用BigDecimal
本文将从一个实际生活场景出发——**购物,通过在买单时计算总消费金额来介绍BigDecimal的基本使用方法(加减乘除等)。由这个生活中的具体例子出发,加深我们对BigDecimal的记忆和理解。
1.1 初始化BigDecimal对象
BigDecimal的源码中有16个构造函数和3个静态方法可以用于初始化BigDecimal对象。如图所示:
本文会重点介绍上图所标记的三种方法。我们先建立一个Receipt类,在该类中定义四个常量并使用main方法将它们打印出来。
从控制台的输出结果中,我们可以看到使用参数类型为String类型的构造函数创建的MEATPRICE对象打印结果为19.98,而使用参数类型为double类型的构造函数创建的RICEPRICE对象打印结果为3.3300000000000000710542735760100185871124267578125(每个机器可能不一样)。当我们的数量足够多的时候,就会产生导致总价产生较大的误差,这是我们不希望看到的。同时,在使用静态方法valueOf的时候,也能**精确的数字。这是因为源码中调用了Double类型的toString方法,然后再调用了参数类型为String类型的构造函数创建该对象。
1.2 BigDecimal的加减乘除等实例方法
在进行下面的操作之前,我们将RICE_PRICE改为使用valueOf方法初始化。我们在main方法中定义了double类型的weightOfMeat、weightOfRice变量和int类型的quantityOfVinda变量。同时,我们定义了一张面值100购物卡,为了方便演示,只是定义BigDecimal的amount对象,并且利用BigDecimal的常量ZERO创建totalPrice对象。
在使用BigDecimal的实例方法时,如multiply(乘法)方法,需要传入BigDecimal类型参数。同样,在加减和除的方法中,也是需要传入BigDecimal类型参数。所以在进行计算前,我们调用了valueOf类方法,用以获取对应数值的BigDecimal对象。从上图中,我们可以看出“猪肉总价为:44.9550”。虽然计算结果正确,但是却不符合我们实际生活需求。因为在**的小票上,数字只有两位小数,所以我们在计算riceTotalPrice时,调用了实例方法setScale,该方法一共有三个重载方法,最常用的就是我们所使用的这种。在设置了小数点位数和舍入**后,我们可以看到“大米总价为:84.42”,84.42才符合我们的实际需求。接下来,我们将通过BigDecimal的add(加法)计算出totalPrice,再通过BigDecimal的subtract(减法)计算amount减去totalPrice,**BigDecimal的differencePrice(差价)对象。通过BigDecimal的compareTo方法比较difference与BigDecimal.ZERO的大小关系,得出客户使用了购物卡后是否还需再付钱,如还要付钱,将调用BigDecimal的abs方法将difference取绝对值。
在这一次的购物的过程中,我们学会了BigDecimal的初始化方法,加减法,乘法和compareTo方法。不过,在比较两个值是否相等还有equals方法,这个方法要谨慎使用。因为这个equals方法不仅要比较数值是否相同,还要比较精度是否相同。也就是说,如果2.0和2.00用equals方法进行比较,返回的结果为false。到目前为止,还有一个常用的方法——divide(除法),我们没学到。现在,我们就来使用divide算一算是买清风卷纸划算还是买维达卷纸划算。
通过使用divide方法,我们计算出了qingFengSingle和vindaSingle。在调用divide的过程中,我们指定了保留的小数点位数和舍入**。在BIgDecimal中divide一共有6个重载方法,其形参不同之处在于是否有精度参数,是否有舍入**参数及舍入**参数的类型。
1.3 八种舍入**
在上面的代码中,我们在为setScale或divide指定舍入**参数时,是通过RoundingMode的枚举指定的。RoundingMode的枚举对象一共有8个,分别为:RoundingMode.UP、RoundingMode.DOWN、RoundingMode.CEILING、RoundingMode.FLOOR、RoundingMode.HALFUP、RoundingMode.HALFDOWN、RoundingMode.HALF_EVEN和RoundingMode.UNNECESSARY。RoundingMode枚举类有一个类型为int的oldModel变量,从0-7依次对应上面8种枚举对象。关于枚举类型的简单介绍,可参考博主的《JAVA枚举类型(Enum)的使用》
RoundingMode.UP:舍入远离零的舍入**。在丢弃非零部分之前始终**数字(始终对非零舍弃部分前面的数字加1)。注意,此舍入**始终不会**计算值的大小。RoundingMode.DOWN:接近零的舍入**。在丢弃某部分之前始终不**数字(从不对舍弃部分前面的数字加1,即截短)。注意,此舍入**始终不会**计算值的大小。RoundingMode.CEILING:接近正无穷大的舍入**。如果 BigDecimal 为正,则舍入行为与 ROUNDUP 相同;如果为负,则舍入行为与 ROUNDDOWN 相同。注意,此舍入**始终不会**计算值。RoundingMode.FLOOR:接近负无穷大的舍入**。如果 BigDecimal 为正,则舍入行为与 ROUNDDOWN 相同;如果为负,则舍入行为与 ROUNDUP 相同。注意,此舍入**始终不会**计算值。RoundingMode.HALFUP:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入**。如果舍弃部分 >= 0.5,则舍入行为与 ROUNDUP 相同;否则舍入行为与 ROUND_DOWN 相同。注意,这是我们在小学时学过的舍入**(四舍五入)。RoundingMode.HALFDOWN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入**。如果舍弃部分 > 0.5,则舍入行为与 ROUNDUP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。RoundingMode.HALFEVEN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUNDHALFUP 相同;如果为偶数,则舍入行为与 ROUNDHALF_DOWN 相同。注意,在重复进行一系列计算时,此舍入**可以将累加错误减到最小。此舍入**也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。如果前一位为奇数,则入位,否则舍去。以下例子为保留小数点1位,那么这种舍入**下的结果。1.15 ==> 1.2 ,1.25 ==> 1.2RoundingMode.UNNECESSARY:断言请求的操作具有精确的结果,因此不需要舍入。如果对**精确结果的操作指定此舍入**,则抛出ArithmeticException。二. 总结
对于BigDecimal类,我们已经**了基本的用法,这些用法也是开发过程中最常见的用法。BigDecimal是可以绝对控制小数点位数的一种数据类型,它适用于银行金融,商城类业务开发。同时,作为一种数据类型,其最根本的功能就是对数据进行运算,这也是本文的核心内容。参考网址:BigDecimal加减乘除计算;廖雪峰的官方网站。代码:
public class Receipt { //猪肉单价(单位:kg) private static final BigDecimal MEAT_PRICE = new BigDecimal("19.98"); //大米单价(单位:kg) private static final BigDecimal RICE_PRICE = new BigDecimal(3.33D); //清风卷纸(12包装)单价 private static final BigDecimal QINGFENG_ROLL_PAPER_PRICE = BigDecimal. valueOf(25D); //维达卷纸(20包装)单价 private static final BigDecimal VINDA_ROLL_PAPER_PRICE = BigDecimal.valueOf(50D); public static void main(String[] args) { //购物卡余额:100 BigDecimal amount = BigDecimal.valueOf(100D); //总价初始化为0 BigDecimal totalPrice = BigDecimal.ZERO; //猪肉净含量 double weightOfMeat = 2.25D; //大米净含量 double weightOfRice = 25.35D; //卷纸数量 int quantityOfVinda = 1; //猪肉总价:44.9550 BigDecimal meatTotalPrice = MEAT_PRICE.multiply(BigDecima.valueOf( weightOfMeat)).setScale(2, RoundingMode.HALF_UP); //大米总价:84.42 // 3.33 * 25.35 = 84.4155,通过setScale设置保留的2位小数,并且设置舍入**为四舍五入 BigDecimal riceTotalPrice = RICE_PRICE.multiply(BigDecimal.valueOf(weightOfRice)).setScale(2, RoundingMode.HALF_UP); //维达卷纸总价:50.00 BigDecimal vindaRollPaperPrice = VINDA_ROLL_PAPER_PRICE.multiply(BigDecimal.valueOf(quantityOfVinda)).setScale(2, RoundingMode. HALF_UP); //通过add(加法)计算总价totalPrice:179.38 totalPrice = totalPrice.add(meatTotalPrice).add(riceTotalPrice).add( vindaRollPaperPrice); //通过subtract(减法)计算差价differencePrice:-79.38 BigDecimal differencePrice = amount.subtract(totalPrice); if (differencePrice.compareTo(BigDecimal.ZERO) < 0) { System.out.println("请付款:" + differencePrice.abs() + "元," + "购物卡余额:0元."); } else{ System.out.println("购物卡余额:" + differencePrice + "元."); } //通过divide(除法)计算一包清风纸的价格,设置采用四舍五入**保留2位小数,并使用doubleValue方法将结果转化成double类型 double qingFengSingle = QINGFENG_ROLL_PAPER_PRICE.divide(BigDecimal.valueOf(12),2, RoundingMode.HALF_UP).doubleValue(); //通过divide(除法)计算一包维达纸的价格, double vindaSingle = VINDA_ROLL_PAPER_PRICE.divide(BigDecimal.valueOf(20),2, RoundingMode.HALF_UP).doubleValue(); int result = Double.compare(qingFengSingle, vindaSingle); if (result < 0) { System.out.println("一包清风纸价钱" + qingFengSingle + "元,小于一包维达纸价钱"+ vindaSingle + "元。所以购买清风更划算!"); }else if (result == 0){ System.out.println("一包清风纸价钱" + qingFengSingle + "元,等于一包维达纸价钱" + vindaSingle ); }else { System.out.println("一包清风纸价钱" + qingFengSingle + "元,大于一包维达纸价钱" + vindaSingle + "元。所以购买维达更划算!"); } }}
原文:https://mp.weixin.qq.com/s/tOMiRaXJbporc9ChdgzNFw
作者: cauchy6317
来源:
免费分享Java技术资料,需要的小伙伴在后台私信我即可
原文:https://mp.weixin.qq.com/s/tOMiRaXJbporc9ChdgzNFw
作者: cauchy6317
来源: