我是靠谱客的博主 贪玩荷花,最近开发中收集的这篇文章主要介绍用了BigDecimal后,计算结果一定精确?(关于BigDecimal的几个总结),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

    • 问题一:BigDecimal不能使用equals方法做等值比较
      • 1.1、例子:使用equals()来比较BigDecimal
      • 1.2、标度(精度)问题
      • 1.3、例子:使用compareTo()来比较BigDecimal
    • 问题二:BigDecimal使用double初始化时存在精度风险
      • 2.1、BigDecimal如何精确计数?
      • 2.2、BigDecimal(double)有什么问题

BigDecimal,是java.math包中提供的一种可以用来进行更高精度运算的类型,相较于double、float这些类型来说,BigDecimal在和金额计算打交道应该说有着天然的优势,接下来我们一起来分析下BigDecimal中的哪些注意事项

  1. BigDecimal不能使用equals方法做等值比较
  2. BigDecimal使用double初始化时存在精度风险

问题一:BigDecimal不能使用equals方法做等值比较

知道大家有没有注意到,在《阿里巴巴Java开发手册》中其实也有注明

在比较BigDecimal的时候,千万不要用 == 这种方式来,这个应该不用多说吧,BigDecimal属于对象,不是基本类型,不能用 == 来比较。

但是equals可以来比较对象,但是!用equals来比较BigDecimal 也有问题,因为我们的目的是比较数值的大小

那该如何比较呢?自定义个类,继承BigDecimal,重写equals,当然可以。但是其实有更好的办法,在BigDecimal内部提供了compareTo方法买这个方法可以直接判断两个数字的值,相等则返回0

1.1、例子:使用equals()来比较BigDecimal

我们来看个例子,使用equals来比较BigDecimal:

public static void main(String[] args) {
  BigDecimal bigDecimal1 = new BigDecimal(1);
  BigDecimal bigDecimal2 = new BigDecimal(1);
  System.out.println(bigDecimal1.equals(bigDecimal2));

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

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

}
//输出结果:
true
true
false

看输出结果,有的时候结果是true,有的时候结果却是false,很奇怪,为什么呢?我们来看下BigDecimal的equals的源码:

public boolean equals(Object x) {
    if (!(x instanceof BigDecimal))
        return false;
    BigDecimal xDec = (BigDecimal) x;
    if (x == this)
        return true;
    if (scale != xDec.scale)
        return false;
    long s = this.intCompact;
    long xs = xDec.intCompact;
    if (s != INFLATED) {
        if (xs == INFLATED)
            xs = compactValFor(xDec.intVal);
        return xs == s;
    } else if (xs != INFLATED)
        return xs == compactValFor(this.intVal);
    return this.inflated().equals(xDec.inflated());
}

里面有一个scale标度(精度)的比较,大概这就是为什么bigDecimal5和bigDeclmal6的比较结果是false的原因了。equals不仅会比较数值,还会比较这个标度(精度)是否一样。依此,引申出标度(精度)问题,见下。

1.2、标度(精度)问题

BigDecimal使用equals进行比较的时候会比较数值大小和scale标度(精度)。

问题:那为什么上面的bigDecimal1和2、bigDecimal3和4却是相同的呢,难道是因为他们的类型是int、long,而bigDecimal5和6的类型是string,导致出现精度问题?

BigDecimal有四种定义的类型,包括int、long、double、String四种,首先int和long类型都是整数,标度都是0。当类型是double的时候,new Bigdecimal(double) => new BigDecimale(0.1),实际传入的是0.1000000000000000055511151231527827021181583404541015625,这个时候的标度(精度)就是55,也就是小数点后面的个数。

而对于 new BigDecimal(1.0)来说,实际上就是整数,也就是不存在后缀,所以和整数的标度大小是一样的。

对于BigDecimal(String)来说,当我们传入一个字符串的时候,如:new BigDecimal(“0.1”) 创建一个BigDecimal的时候,其实创建出来的值正好就是等于0.1的,那么他的标度(精度)也就是1。如果使用的是new BigDecimal(“0.10000”),此时标度就是5,所以这也就是解释了为什么最后的bigDecimal5和6的结果不一样咯。

1.3、例子:使用compareTo()来比较BigDecimal

那如何解决呢?其实BigDecimal不仅提供了equals方法,还提供了一个compareTo()方法,这个方法其实就是只比较两个数值的大小,相等则返回0 ,看例子:

public static void main(String[] args) {
  BigDecimal bigDecimal1 = new BigDecimal(1);
  BigDecimal bigDecimal2 = new BigDecimal(1);
  System.out.println(bigDecimal1.compareTo(bigDecimal2));

  BigDecimal bigDecimal3 = new BigDecimal(1);
  BigDecimal bigDecimal4 = new BigDecimal(1.0);
  System.out.println(bigDecimal3.compareTo(bigDecimal4));

  BigDecimal bigDecimal5 = new BigDecimal("1");
  BigDecimal bigDecimal6 = new BigDecimal("1.0");
  System.out.println(bigDecimal5.compareTo(bigDecimal6));

}
//输出结果:
0
0
0

问题二:BigDecimal使用double初始化时存在精度风险

BigDecimal使用double初始化时存在精度风险,那这是怎么一回事呢?其实在阿里开发手册中也有这么一条建议,或者说是要求吧

禁止使用构造方法BigDecimal(double)的方式把double值转化成BigDecimal对象。

我们知道,计算机是只认识二进制的,只认识0和1,也就是说任何数据都会转化成0和1存储在计算机中,整数简单,除二取余,逆序排列即可。而小数则不一定全部能转化成二进制,比如0.1,在转换的过程中会出现循环的情况,所以这种事无法正确的存储完整的数据的,计算机是无法精确的存储这种数据的,所以计算机采用的是一定的精度来解决这个问题的,这就是IEEE 754(IEEE二进制浮点数算术标准)规范的主要思想。

IEEE 754规定了多种表示浮点数值的方式,其中最常用的就是32位单精度浮点数和64位双精度浮点数。

在Java中,使用float和double分别用来表示单精度浮点数和双精度浮点数。

所谓精度不同,可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。

2.1、BigDecimal如何精确计数?

如果大家看过BigDecimal的源码,其实可以发现,实际上一个BigDecimal是通过一个"无标度值"和一个"标度"来表示一个数的。

在BigDecimal中,标度(精度)是通过scale字段来表示的。

而无标度值的表示比较复杂。当unscaled value超过阈值 (默认为Long.MAX_VALUE) 时采用intVal字段存储unscaled value,intCompact字段存储Long.MIN_VALUE,否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算,intVal为空。

Long.MAX_VALUE = 0x8000000000000000L;
Long.MIN_VALUE = 0x7fffffffffffffffL;

涉及到的字段就是这几个:

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

    /**
     * If the absolute value of the significand of this BigDecimal is
     * less than or equal to {@code Long.MAX_VALUE}, the value can be
     * compactly stored in this field and used in computations.
     */
    private final transient long intCompact;
}

BigDecimal主要是通过一个无标度值和标度来表示的。

那么标度到底是什么呢?除了scale这个字段,在BigDecimal中还提供了scale()方法,用来返回这个BigDecimal的标度。那么,scale到底表示的是什么,其实代码中的注释已经说的很清楚了:

  • 如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。

如123.123,那么如果使用BigDecimal表示,那么他的无标度值为123123,他的标度为3。

而二进制无法表示的0.1,使用BigDecimal就可以表示了,及通过无标度值1和标度1来表示。

我们都知道,想要创建一个对象,需要使用该类的构造方法,在BigDecimal中一共有以下4个构造方法:

其中 BigDecimal(int) 和 BigDecimal(long) 比较简单,因为都是整数,所以他们的标度都是0。而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了。

下面来看一下BigDecimal(double)有什么问题

2.2、BigDecimal(double)有什么问题

BigDecimal中虽然提供了一个通过double创建BigDecimal的方法,但是这其中也挖下了一个坑

我们知道,double表示的小数是不精确的,比如0.1这个数值,double只能表示他的近似值,所以当我们使用new BigDecimal(0.1)的时候,实际上创建出来的数值并不是正好等于0.1的,而是一个近似值。就如阿里开发手册中解释的那样。

所以,如果我们在代码中,使用BigDecimal(double) 来创建一个BigDecimal的话,那么是损失了精度的,这是极其严重的。

那么,该如何创建一个精确的BigDecimal来表示小数呢,答案是使用String创建。

而对于BigDecimal(String) ,当我们使用new BigDecimal(“0.1”)创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。

那么他的标度也就是1。

但是需要注意的是,new BigDecimal(“0.10000”)和new BigDecimal(“0.1”)这两个数的标度分别是5和1,如果使用BigDecimal的equals方法比较,得到的结果是false,可以使用compareTo方法进行比较

那么,想要创建一个能精确的表示0.1的BigDecimal,请使用以下两种方式:

BigDecimal recommend1 = new BigDecimal("0.1");

BigDecimal recommend2 = BigDecimal.valueOf(0.1);

最后

以上就是贪玩荷花为你收集整理的用了BigDecimal后,计算结果一定精确?(关于BigDecimal的几个总结)的全部内容,希望文章能够帮你解决用了BigDecimal后,计算结果一定精确?(关于BigDecimal的几个总结)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(62)

评论列表共有 0 条评论

立即
投稿
返回
顶部