BigDecimal的认识

避免用Double来进行运算     

        使用Double来计算,我们以为的算术运算和计算机计算的并不完全一直,这是因为计算机是以二进制存储数值的,我们输入的十进制数值都会转换成二进制进行计算,十进制转二进制再转换成十进制就不是原来那个十进制了,再也不是曾经那个少年了。举个例子:十进制的0.1转换成二进制是0.0 0011 0011 0011...(无数个0011),再转换成十进制就是0.1000000000000000055511151231。

计算机无法精确地表达浮点数,这是不可避免的,这是为什么浮点数计算后精度损失的原因。

BigDecimal你遇见过哪些坑?

还是通过一个简单的例子,计算上边例子中的运算,来看一下结果:

        通过简单的例子,我们发现精度损失并不是很大,但是这并不代表我们可以使用,特别是电商类系统中,每天少说几百万的单量,每笔订单哪怕少计算一分钱,算下来也是一笔不小的金额,所以说,这不是个小事情,然后很多人就说,金额计算啊,你用BigDecimal啊,对的,这个没毛病,但是用了BigDecimal就完事大吉了吗?当问出这句话的时候,就说明这其中必有蹊跷。

我们发现使用了BigDecimal之后计算结果还是不精确,这里就要记住BigDecimal的第一个坑了:

BigDecimal来表示和计算浮点数的时候,要使用String的构造方法来初始化BigDecimal。

改进一下

 浮点数的舍入和格式化该如何选择?

         我们首先来看看使用String.format的格式化舍入,会有什么结果,我们知道浮点数有double和float两种,下边我们就用这两种来举例子:

        得到的结果似乎与我们的预期有出入,其实这个问题也很好解释,double和float的精度是不同的,double的3.35相当于3.350000000000000088817841970012523233890533447265625,而float的3.35相当于3.349999904632568359375,String.format才有的又是四舍五入的方式舍入,所以精度问题和舍入方式就导致了运算结果与我们预期不同。 

        Formatter类中默认使用的是HALF_UP的舍入方式,如果我们需要使用其他的舍入方式来格式化,可以手动设置。

        到这里我们就知道通过String.format的方式来格式化这条路坑有点多,所以,「浮点数的字符串格式化还得要使用BigDecimal来进行」

BigDecimal不能使用equals方法比较? 

if(bigDecimal == bigDecimal1){
  // 两个数相等
}

这种错误,一眼就可以看出问题,因为BigDecimal是对象,所以不能用==来判断两个数字的值是否相等。

以上这种问题,在有一定的经验之后,还是可以避免的,但是看一下以下这行代码,你觉得他有问题吗:

if(bigDecimal.equals(bigDecimal1)){
  // 两个数相等
}

可以明确的告诉大家,以上这种写法,可能得到的结果和你预想的不一样!

BigDecimal bigDecimal = new BigDecimal(1);
BigDecimal bigDecimal1 = new BigDecimal(1);
System.out.println(bigDecimal.equals(bigDecimal1));


BigDecimal bigDecimal2 = new BigDecimal(1);
BigDecimal bigDecimal3 = new BigDecimal(1.0);
System.out.println(bigDecimal2.equals(bigDecimal3));


BigDecimal bigDecimal4 = new BigDecimal("1");
BigDecimal bigDecimal5 = new BigDecimal("1.0");
System.out.println(bigDecimal4.equals(bigDecimal5));

以上代码,输出结果为:

true
true
false

BigDecimal的equals原理

        通过以上代码示例,我们发现,在使用BigDecimal的equals方法对1和1.0进行比较的时候,有的时候是true(当使用int、double定义BigDecimal时),有的时候是false(当使用String定义BigDecimal时)。

 那么,为什么会出现这样的情况呢,我们先来看下BigDecimal的equals方法。

尝试着对代码进行debug,在debug的过程中我们也可以看到num1的精度时0,而num2的精度是1。

        按照我们的理解1和1.0是相等的,也应该是相等的,但是Bigdecimal的equals在比较中不只是比较了value,还比较了scale,scale是小数点后的位数,明显两个值的小数点后位数不一样,所以结果为false。

        实际的使用中,我们常常是只希望比较两个BigDecimal的value,这里就要注意,要使用compareTo方法:结果为-1表示小于,0表示等于,1表示大于

为什么精度不同

        首先,BigDecimal一共有以下4个构造方法:

BigDecimal(int)
BigDecimal(double) 
BigDecimal(long) 
BigDecimal(String)

BigDecimal(long) 和BigDecimal(int)

首先,最简单的就是BigDecimal(long) 和BigDecimal(int),因为是整数,所以精度就是0 :

public BigDecimal(int val) {
  this.intCompact = val;
  this.scale = 0;
  this.intVal = null;
}

public BigDecimal(long val) {
  this.intCompact = val;
  this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
  this.scale = 0;
}

BigDecimal(double)

        而对于BigDecimal(double) ,当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是整好等于0.1的,而是0.1000000000000000055511151231257827021181583404541015625 。这是因为doule自身表示的只是一个近似值。

        那么,无论我们使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义,他的近似值都是0.1000000000000000055511151231257827021181583404541015625这个,那么他的精度就是这个数字的位数,即55

        其他的浮点数也同样的道理。对于new BigDecimal(1.0)这样的形式来说,因为他本质上也是个整数,所以他创建出来的数字的精度就是0。

        所以,因为BigDecimal(1.0)和BigDecimal(1.00)的精度是一样的,所以在使用equals方法比较的时候,得到的结果就是true。

BigDecimal(string)

        而对于BigDecimal(string) ,当我们使用new BigDecimal(“0.1”)创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。那么他的精度也就是1。

如果使用new BigDecimal(“0.10000”),那么创建出来的数就是0.10000,精度也就是5。

        所以,因为BigDecimal(“1.0”)和BigDecimal(“1.00”)的精度不一样,所以在使用equals方法比较的时候,得到的结果就是false。        

BigDecimal的方法

         java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算,双精度浮点数变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要精准计算精度的数字,我们可以直接使用float和double处理,但是Double.valueof(String) 和Float.valueof(String)会丢失精度,所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。

        BigDecimal所创建的是对象,所以我们不能使用传统的算术运算符直接对其进行数学运算,而必须调用其相对应的方法,方法中的参数也必须是BigDecimal的对象,构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

BigDecimal常用构造函数

  • BigDecimal(int):创建一个具有参数所指定整数值的对象
  • BigDecimal(double):创建一个参数所指定双精度值的对象  //不推荐使用
  • BigDecimal(long):创建一个具有参数所指定长整数值的对象
  • BigDecimal(String):创建一个具有参数所指定以字符串表示的数值的对象 //推荐使用

注意:浮点数建议使用BigDecimal(String)构造函数,因为Double是不精准的,可以先使用Double.toString(double)方法,再使用BigDecimal(String)方法创建一double为参数的对象

BigDecimal常用方法详解

  • subtract(BigDecimal):BigDecimal对象中的值相减,返回BigDecimal对象
  • add(BigDecimal):BigDecimal对象中的值相加,返回BigDecimal对象 
  • multipiy(BigDecimal):BigDecimal对象中的值相乘,返回BigDecimal对象
  • divide(BigDecimal):BigDecimal对象中的值相除,返回BigDecimal对象
  • remainder(BigDecimal):BigDecimal对象中的值取余,返回BigDecimal对象
  • toString():将BigDecimal对象中的值转换成字符串
  • doubleValue():将BigDecimal对象中的值转换成双精度数
  • floatValue():将BigDecimal对象中的值转换成单精度
  • longValue():将BigDecimal对象中的值转换成长整数
  • intValue():将BigDecimal对象中的值转换成整数
  • compareTo():比较BigDecimal两个值的大小,结果为-1表示小于,0表示等于,1表示大于

BigDecimal格式化

首先创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

NumberFormat currency = NumberFormat.getCurrencyInstance();
NumberFormat percent = NumberFormat.getPercentInstance();

percent.setMaximumFractionDigits(4);//百分比小数点最多3位

BigDecimal loanAmount = new BigDecimal("15000.48");
BigDecimal interestRate = new BigDecimal("0.00811111111");
BigDecimal interest = loanAmount.multiply(interestRate);

System.out.println("贷款金额"+currency.format(loanAmount));
System.out.println("利率:"+percent.format(interestRate));
System.out.println("利息:"+currency.format(interest));

//输出结果

//贷款金额¥15,000.48
//利率:0.8111%
//利息:¥121.67

BigDecimal常见异常:除法时,当不整除,出现无限循环小数时,就会抛异常。

BigDecimal a = new BigDecimal(0.1);
BigDecimal b = new BigDecimal(0.3);
System.out.println(a.divide(b));

上面就会抛出以下异常

Non-terminating decimal expansion; no exact representable decimal result.
	at java.math.BigDecimal.divide(BigDecimal.java:1690)

解决办法  divide方法设置精确的小数点,如:divide(XXXX,2,1)

BigDecimal a = new BigDecimal(0.1);
BigDecimal b = new BigDecimal(0.3);
System.out.println(a.divide(b, 2, 1));//0.33

以上内容属于个人笔记整理,如有错误,欢迎批评指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值