前言

今天不说废话,直接上代码:

public class Main {
    public static void main(String[] args) {
        float f1 = 2.0f;
        float f2 = 1.1f;
        System.out.println("f1 - f2 = " + (double)(f1 - f2));
        System.out.println("f1 - f2 == 0.9 ? " + (f1 - f2 == 0.9));

        double d1 = 2.0;
        double d2 = 1.1;
        System.out.println("d1 - d2 = " + (d1 - d2));
        System.out.println("d1 - d2 == 0.9 ? " + (d1 - d2 == 0.9));

        Float F1 = 2.0f;
        Float F2 = 1.1f;
        System.out.println("F1 - F2 = " + (F1 - F2));
        System.out.println("F1 - F2 == 0.9 ? " + (F1 - F2 == 0.9));

        Double D1 = 2.0;
        Double D2 = 1.1;
        System.out.println("D1 - D2 = " + (D1 - D2));
        System.out.println("D1 - D2 == 0.9 ? " + (D1 - D2 == 0.9));
    }
}

这段代码分别使用了浮点型和双精度浮点型来计算2-1.1,来看看他的结果是多少,是不是等于0.9呢?让我们看看运行结果:
image.png
是不是很惊讶,2 - 1.1 != 0.9 ?

计算机中的数据精度问题

要搞明白上面的问题,首先我们要明白这些数据在计算机中是怎么存储的。学过计算机基础的人都知道,计算机只认二进制,我们所有的代码最终都是以二进制的形式在计算机中运行的。计算机在进行运算时,也会将我们的数据转换成二进制来进行运算。
既然要转换成二进制进行运算,那么有些小数便是不能够完全精确的,就好比在10进制中,无法精确表达1/3,同样,在二进制中,无法准确表达1/10,1/10转换成二进制就变成了0.0001100110011001100110011001100110011001100110011001101……,计算机在进行运算的时候,不可能精确地去计算1/10的值,只能将其截断,那么这样,就出现了所谓的精度丢失问题。那么,有没有一种方案,能够解决这种精度丢失问题呢?

BigDecimal

使用BigDecimal进行2.0-1.1运算

最常用的方法就是使用BigDecimal,我们将要操作的浮点通过BigDecimal的构造方法传入字符串或使用valueOf方法传入double类型的操作数转换成BigDecimal对象,再通过BigDecimal内置的运算方法进行运算。

public class Main {
    public static void main(String[] args) {
        BigDecimal b1 = BigDecimal.valueOf(2.0);
        BigDecimal b2 = BigDecimal.valueOf(1.1);
        System.out.println(b1.subtract(b2).doubleValue());
        System.out.println(0.9 == b1.subtract(b2).doubleValue());
    }
}

我们来看看运行结果:
image.png
我们看到,再使用了BigDecimal进行运算后,成功的达到了我们想要的结果。

BigDecimal提高精度原理

我们可能会有一个疑问,为什么使用BigDecimal不会有精度丢失问题呢?难道他不是使用二进制进行计算的吗?
其实不然,计算机中的所有运算都是通过二进制进行的,BigDecimal也一样。不过与传统运算方式不同的是,BigDecimal在进行小数运算时,会将运算数进行放大操作,使其变成一个整数,再在整数的操作维度上进行运算,这样就避免了精度丢失的问题了。

BigDecimal常用方法

valueOf方法

在上面的代码中,我们就使用valueOf方法实例化了两个BigDecimal类,valueOf可以接收一个数字,并返回一个BigDecimal实例。

BigDecimal() 构造方法

我们也可以通过使用BigDecimal的构造方法直接进行实例化,这里我们可以传入一个数字或者一个字符串进行实例化,同样能够返回一个BigDecimal实例。不过需要注意的是,如果直接传入数字的话,仍然可能会造成精度丢失的问题,因此我们在使用构造方法实例对象是,应当传入一段字符串

add、subtract、multiply、divide

  • add方法,进行加法运算,加上传入的BigDecimal对象。
  • subtract方法,进行减法运算,减去传入的BigDecimal对象。
  • multiply方法,进行乘法运算,乘以传入的BigDecimal对象。
  • divide方法,进行除法运算,除以传入的BigDecimal对象。

在上面四种方法,最终都会返回一个新的BigDecimal对象,这个对象就是运算的结果。

toString、doubleValue、floatValue、longValue、intValue

  • toString方法,将BigDecimal对象的值转换成一个字符串对象并返回。
  • doubleValue方法,将BigDecimal对象的值转换成一个双精度浮点型数并返回。
  • floatValue方法,将BigDecimal对象的值转换成一个单精度浮点型数并返回。
  • longValue方法,将BigDecimal对象的值转换成一个长整数并返回。
  • intValue方法,将BigDecimal对象的值转换成一个整数并返回。

compareTo方法比较

我们可以使用compareTo方法对两个BigDecimal对象进行比较;int a = bigdemical.compareTo(bigdemical2)
若:

  • a = -1,表示bigdemical小于bigdemical2;
  • a = 0,表示bigdemical等于bigdemical2;
  • a = 1,表示bigdemical大于bigdemical2;

BigDecimal常见异常

在我们使用BigDecimal进行除法运算时需要注意,若运算无法整数,出现无限循环小数时,便会抛出ArithmeticException异常。
因此,我们可以在使用divide时,设置结果精确的小数点,例如:
int a = bigdemical.divide(bigdemical2,2),此时,在进行bigdemical/bigdemical2时,就会只保留两位小数。