bigdecimal不保留小数_如何使用BigDecimal?

本文介绍了Java中BigDecimal类的使用,包括初始化、加减乘除等操作,以及如何通过setScale和divide方法实现小数点位数控制和舍入模式设置。文章通过超市购物场景举例,详细阐述了BigDecimal在金融、商城类业务开发中的重要性,并总结了八种RoundingMode舍入模式的含义和应用场景。
摘要由CSDN通过智能技术生成

点击上方“方志朋”,选择“设为星标”

做积极的人,而不是积极废人

e4b9fe40c290ff8ff4c272117f54fe70.png

本文来源于读者投稿,已授权原创申明。

前言: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对象。如图所示:8d04615c41a32e5f4fb56f056f1214dd.png本文会重点介绍上图所标记的三种方法。我们先建立一个Receipt类,在该类中定义四个常量并使用main方法将它们打印出来。0762c939d70d9fae7733ac0f02408968.png从控制台的输出结果中,我们可以看到使用参数类型为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对象。b9e3fe736afb3aad52dba3d7d54f96e8.png在使用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取绝对值。792d829be80042bd061b9c088f5a9e61.png在这一次的购物的过程中,我们学会了BigDecimal的初始化方法,加减法,乘法和compareTo方法。不过,在比较两个值是否相等还有equals方法,这个方法要谨慎使用。因为这个equals方法不仅要比较数值是否相同,还要比较精度是否相同。也就是说,如果2.0和2.00用equals方法进行比较,返回的结果为false。到目前为止,还有一个常用的方法——divide(除法),我们没学到。现在,我们就来使用divide算一算是买清风卷纸划算还是买维达卷纸划算。0076ccc613557c35e553e7ef4811ff1d.png通过使用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.2

  • RoundingMode.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(BigDecimal.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 + "元。所以购买维达更划算!");

}

}

}

热门内容:    
  • Java分布式 RPC 框架性能大比拼,Dubbo最差?
  • 面试官:MySQL 表设计要注意什么?

  • 面试官问:平时碰到系统CPU飙高和频繁GC,你会怎么排查?
  • Java 线程池 ThreadPoolExecutor 八种拒绝策略浅析

  • Spring Boot 实现定时任务的 4 种方式
  • Java 程序员常用资源工具集合(建议收藏)

  • 你可能不需要微服务
  • 我在 GitHub 上都见过哪些沙雕项目?

e144b86f87a6bc5e2bc6b29b44d99915.png

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值