大家好,我是程序员大猩猩。
在我们敲代码的时候,有时候会碰到一些莫名其妙的问题。而且是在很简单的俩个浮点数运算的情况下发生。发生错误后,我们脑袋里是不是会有一个大大的问号,心里还在默念,这怎么可能?
我们来看看以下代码,为什么运行结果和我们的想法一致呢?
public static void main(String[] args) {
System.out.println(0.11D + 3001299.32D);
System.out.println(0.11F + 4001299.32F);
}
如上,代码中就是俩个简单的Double和Float的浮点数相加,我们甚至不用运行,就可算出它俩的答案。
第一个答案:3001299.43 另一个答案是: 4001299.43
但是,我们来运行一下这个main方法,看看它的运行结果?
嗯?怎么不对呢?为什么?
因为在 Java 中,double 和 float 类型使用的是二进制浮点数表示法,这种表示法在表示某些十进制小数时可能会出现无限循环或近似值,从而导致精度损失。例如:0.1 在二进制表示中是一个无限循环小数,因此在 double 或 float 类型中无法精确表示。
那么我们该如何避免出现这样的错误呢? 这里就进入我们要说的BigDecimal了,它在Java中不是一个基础类型,而是一个类。
那么为什么BigDecimal可以保证精度计算,不丢精度值呢?
BigDecimal它基于整数或字符串表示的数学上的十进制数,可以精确地表示任何十进制数。
BigDecimal 内部使用一个 int 类型的 scale 来表示小数点后的位数,和一个 BigInteger 类型的 unscaledValue 来表示去掉小数点后的整数部分。这种表示方法允许 BigDecimal 在进行算术运算时保持精确的十进制精度。
// BigDecimal源码
public class BigDecimal extends Number implements Comparable<BigDecimal> {
/**
* The unscaled value of this BigDecimal, as returned by {@link
* #unscaledValue}.
*
* @serial
* @see #unscaledValue
*/
private final BigInteger intVal;
/**
* The scale of this BigDecimal, as returned by {@link #scale}.
*
* @serial
* @see #scale
*/
private final int scale; // Note: this may have any value, so
// calculations must be done in longs
}
当我们使用 BigDecimal 进行加、减、乘、除等运算时,它会在内部进行精确的数学运算,确保结果尽可能精确。在进行除法运算时,由于可能产生除不尽的情况,BigDecimal 允许指定舍入模式(如四舍五入、向上取整、向下取整等)来处理这种情况。
BigDecimal的运算性能比基础运算方式要慢,为什么呢?
a. 因为BigDecimal是一个类,它的产生还包含BigInteger和其他元数据。
b. 既然它是一个类,那么它的创建和销毁都要消耗虚拟机资源。
c. BigDecimal属于十进制的运算,它为了使结果精确精度值的准确度,势必需要更多的运算时间。而基础运算方式是二进制的,它是在电脑硬件上的数据计算,肯定快。
那么我们为什么还要用BigDecimal来计算精度值呢?
因为它损耗的性能,完全可以弥补我们在基础运算方式上无法获取准确精度的错误。第二个还有BigDecimal内还包含很多方法,可以使我们能自由的选择我们所需的浮点数保留的浮点精度。
如何使用BigDecimal?
BigDecimal方法内包含add subtract multiply divide 加减乘除四种基础操作,且包含setScale精度控制的方法。
public static void main(String[] args) {
BigDecimal a = new BigDecimal("3001299.32");
BigDecimal b = new BigDecimal("0.11");
// 加法
BigDecimal add = a.add(b);
System.out.println("加法结果:" + add);
// 减法
BigDecimal subtract = a.subtract(b);
System.out.println("减法结果:" + subtract);
// 乘法
BigDecimal multiply = a.multiply(b);
System.out.println("乘法结果:" + multiply);
// 除法并使结果结果保留两位小数,四舍五入
BigDecimal divide = a.divide(b, 2, RoundingMode.HALF_UP);
System.out.println("除法结果:" + divide);
}
当然,在使用BigDecimal时,一定要注意以下错误情况
一、使用错误的传参或者构造函数
// 源码
public BigDecimal(double val) {
this(val,MathContext.UNLIMITED);
}
以上传入double类型的源码,我们尽量少用,因为double本身就有精度问题,那么我们使用传入double的BigDecimal构造函数,可能会导致精度损失,我们建议使用字符串或者整数构造BigDecimal对象,如下。
BigDecimal a = new BigDecimal("3001299.32");
二、BigDecimal在使用除法时,一定要默认保留精度。
BigDecimal divide = a.divide(b, 2, RoundingMode.HALF_UP);
不用使用以下代码,可能会报错的。
BigDecimal divide = a.divide(b);
三、不要使用equals来比较俩个BigDecimal,请使用compareTo
// 错误的, 永远是false
if(a.equals(b)){
}
// 正确使用
if(a.compareTo(b) < 0){
System.out.println("a小于b");
}
if(a.compareTo(b) == 0){
System.out.println("a等于b");
}
if(a.compareTo(b) > 0){
System.out.println("a大于b");
}
if(a.compareTo(b) > -1){
System.out.println("a大于等于b");
}
if(a.compareTo(b) < 1){
System.out.println("a小于等于b");
}如上代码,正确使用大小比较的方法。
如上,这里介绍了BigDecimal的使用和常见错误问题识别和正确使用方法,再见。