探究Java中BigDecimal

BigDecimal

BigDecimal是什么

摘抄自- - - 百度百科

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

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

BigDecimal 源码中继承实现

package java.math;

public class BigDecimal extends Number implements Comparable<BigDecimal> {
	//......
}

为什么要用BigDecimal

浮点型明明已经有了double和float,为啥还要BigDecimal嘞,这个问题可能我们听的最多的就是Double会丢失精度,主要原因是它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。BigDecimal就不会,我们也知道一般数据库存钱的话就用BigDecimal,而不是其他。因为商业计算往往要求结果精确所以BigDecimal更加适合商业使用

BigDecimal常用构造

分为大类型的有以下几类

  • BigDecimal(int)

    • 创建一个具有参数所指定整数值的对象
  • BigDecimal(double)

    • 创建一个具有参数所指定双精度值的对象
  • BigDecimal(long)

    • 创建一个具有参数所指定长整数值的对象
  • BigDecimal(String)

    • 创建一个具有参数所指定以字符串表示的数值的对象

每个构造函数中都有不同的参数,可以看下图

探究Java中BigDecimal_第1张图片

构建简单的实例

BigDecimal a = new BigDecimal("1.3");
System.out.println("a = "+a); 
BigDecimal b =new BigDecimal(1.3);
System.out.println("b = "+b); 
//结果:
//a = 1.3
//b = 1.3000000000000000444089209850062616169452667236328125

BigDecimal常用方法

BigDecimal 对于常用的加,减,乘,除,BigDecimal类提供了相应的成员方法。

public BigDecimal add(BigDecimal value);                  //加法
public BigDecimal subtract(BigDecimal value);             //减法 
public BigDecimal multiply(BigDecimal value);             //乘法
public BigDecimal divide(BigDecimal value);               //除法

使用案例

BigDecimal bigDecimal1 = new BigDecimal(9);
BigDecimal bigDecimal2 = new BigDecimal(3);
System.out.println("add = "+ bigDecimal1.add(bigDecimal2));//add = 12
System.out.println("subtract = "+ bigDecimal1.subtract(bigDecimal2));//subtract = 6
System.out.println("multiply = "+ bigDecimal1.multiply(bigDecimal2));//multiply = 27
System.out.println("divide = "+ bigDecimal1.divide(bigDecimal2));//divide = 3

BigDecimal的两值比较

一般情況下比较两个值,可能会想起来equals,但是相对于BigDecimal而言,compareTo更加合适

compareTo和equals的区别。

BigDecimal decimal1 = new BigDecimal("1");
BigDecimal decimal2 = new BigDecimal("1.00");
System.out.println(decimal1.equals(decimal2));//false
System.out.println(decimal1.compareTo(decimal2));//0

BigDecimal的equals方法当精度不一样的时候也当做不相等,而compareTo方法却可以忽略精度的不同,只比较数值是否相同

小结:

  • equals : equals 方法比较value和scale(数值和精度),精度不一样,也返回false。
  • compareTo:数值相同但是精度不同的两个数(例如2.0和2.00)被认为是相等的两个数,返回0另外对于a.compareTo(b)方法
    • a
    • a=b,返回0
    • a>b, 返回1

compareTo

public int compareTo(BigDecimal val) {
    // Quick path for equal scale and non-inflated case.
    if (scale == val.scale) {
        long xs = intCompact;
        long ys = val.intCompact;
        if (xs != INFLATED && ys != INFLATED)
            return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
    }
    int xsign = this.signum();
    int ysign = val.signum();
    if (xsign != ysign)
        return (xsign > ysign) ? 1 : -1;
    if (xsign == 0)
        return 0;
    int cmp = compareMagnitude(val);
    return (xsign > 0) ? cmp : -cmp;
}

compareTo方法的作用是BigDecimal大小的比较。从上面源码中看出,两个值比较,最终返回的是一个int类型数字,而不是true和false。

一般两个值比较的时候无非就是三大种情况,大于、小于、等于,与之对应的是 1 、-1、 0。

  • 大于 = 1

  • 小于 = -1

  • 等于 = 0

当然还有两种小情况

  • 大于等于 = >-1
  • 小于等于 =<1

举个例子:==

public static void main(String[] args) {
    BigDecimal a = new BigDecimal (101);
    BigDecimal b = new BigDecimal (103);


    if(a.compareTo(b) == -1){
        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");
    }

    if(a.compareTo(b) < 1){
        System.out.println("a小于等于b");
    }
}

注意:使用compareTo方法比较,a、b均不能为null,否则会报空指针

BigDecimal 常见的问题

divide方法进行除法时当不整除抛出异常

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result

原因分析:

​ 通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

解决方法:

​ divide方法设置精确的小数点,如:divide(xxxxx,2)

BigDecimal一定不会丢失精度吗?

在使用 BigDecimal 时,使用它的 BigDecimal(String) 构造器创建对象才有意义。其他的如 BigDecimal b = new BigDecimal(3) 这种,还是会发生精度丢失的问题

BigDecimal a = new BigDecimal(1.01);
BigDecimal b = new BigDecimal(1.02);
BigDecimal c = new BigDecimal("1.01");
BigDecimal d = new BigDecimal("1.02");
System.out.println(a.add(b));
System.out.println(c.add(d));

//输出结果
2.0300000000000000266453525910037569701671600341796875
2.03

通过上面的案例可以看出,在使用BigDecimal(double)中构造器的时候同样会丢失精度,但是使用 Bigdecimal 的 BigDecimal(String) 构造器的变量在进行运算的时候却没有出现这种问题。

上面的问题我们从源码的注释中官方也给出了说明,如下是 BigDecimal 类的 double 类型参数的构造器上的一部分注释说明:

/**
 * Translates a {@code double} into a {@code BigDecimal} which
 * is the exact decimal representation of the {@code double}'s
 * binary floating-point value.  The scale of the returned
 * {@code BigDecimal} is the smallest value such that
 * (10scale × val) is an integer.
 * 

* Notes: *

    *
  1. * The results of this constructor can be somewhat unpredictable. * One might assume that writing {@code new BigDecimal(0.1)} in * Java creates a {@code BigDecimal} which is exactly equal to * 0.1 (an unscaled value of 1, with a scale of 1), but it is * actually equal to * 0.1000000000000000055511151231257827021181583404541015625. * This is because 0.1 cannot be represented exactly as a * {@code double} (or, for that matter, as a binary fraction of * any finite length). Thus, the value that is being passed * in to the constructor is not exactly equal to 0.1, * appearances notwithstanding. * *
  2. * The {@code String} constructor, on the other hand, is * perfectly predictable: writing {@code new BigDecimal("0.1")} * creates a {@code BigDecimal} which is exactly equal to * 0.1, as one would expect. Therefore, it is generally * recommended that the {@linkplain #BigDecimal(String) * String constructor} be used in preference to this one. * *
  3. * When a {@code double} must be used as a source for a * {@code BigDecimal}, note that this constructor provides an * exact conversion; it does not give the same result as * converting the {@code double} to a {@code String} using the * {@link Double#toString(double)} method and then using the * {@link #BigDecimal(String)} constructor. To get that result, * use the {@code static} {@link #valueOf(double)} method. *
* * @param val {@code double} value to be converted to * {@code BigDecimal}. * @throws NumberFormatException if {@code val} is infinite or NaN. */
public BigDecimal(double val) { this(val,MathContext.UNLIMITED); }

第一段也说的很清楚它只能计算的无限接近这个数,但是无法精确到这个数。

第二段则说,如果要想准确计算这个值,那么需要把 double 类型的参数转化为 String 类型的。并且使用 BigDecimal(String) 这个构造方法进行构造。去获取结果。

BigDecimal 总结

在需要精确的小数计算时再使用BigDecimal,BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。

尽量使用参数类型为String的构造函数。

BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。

你可能感兴趣的:(Java)