Java正确比较浮点数的方法

 更新时间:2020年11月09日 09:00:34   作者:平酱  
这篇文章主要介绍了Java正确比较浮点数的方法,帮助大家更好的利用Java比较浮点数数据,感兴趣的朋友可以了解下

看下面这段代码,将 d1 和 d2 两个浮点数进行比较,输出的结果会是什么?

double d1 = .1 * 3;
double d2 = .3;
System.out.println(d1 == d2);

按照正常逻辑来看,d1经过计算之后的结果应该是0.3,最后打印的结果应该是 true,对吧?但是运行一下就会发现结果并不是 true 而是 false

输出一下 d1,发现得到的答案不是想象中的 0.3 而是 0.30000000000000004,所以和 d2 进行比较结果自然是 false

如何正确地比较浮点数(单精度的 float 和双精度的 double),不单单是 Java 特定的问题,在计算机的内存中,存储浮点数时使用的是 IEEE 754 标准,就会有精度的问题。

存储和转换的过程中浮点数容易引起一些较小的舍入误差,正是这个原因,导致在比较浮点数的时候,不能使用“==”操作符——要求严格意义上的完全相等。

那么如何正确的比较浮点数呢?这里有两种方案。

  • 第一种方案是允许两个值之间存在一点误差(指定一个阈值),使用 Math.abs() 方法来计算两个浮点数之间差异的绝对值,如果这个差异在阈值范围之内,我们就认为两个浮点数是相等的。
final double THRESHOLD = .0001;

double d1 = .1 * 3;
double d2 = .3;

if(Math.abs(d1-d2) < THRESHOLD) {
	System.out.println("d1 和 d2 相等");
} else {
	System.out.println("d1 和 d2 不相等");
}

Math.abs() 方法用来返回 double 的绝对值,如果 double 小于 0,则返回 double 的正值,否则返回 double。也就是说,abs() 后的结果绝对大于 0,如果结果小于阈值(THRESHOLD),我们就认为 d1 和 d2 相等。

  • 第二种方案是使用 BigDecimal 类,可以指定要舍入的模式和精度,这样就可以解决舍入的误差。

以使用 BigDecimal 类的 compareTo() 方法对两个数进行比较,该方法将会忽略小数点后的位数,怎么理解这句话呢?比如说 2.0 和 2.00 的位数不同,但它俩的值是相等的。

a.compareTo(b) 如果 a 和 b 相等,则返回 0,否则返回 -1。

tips: 不要使用 equals() 方法对两个 BigDecimal 对象进行比较,这是因为 equals() 方法会考虑位数,如果位数不同,则会返回 false,尽管数学值是相等的。

BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");

System.out.println(a.equals(b));
System.out.println(a.compareTo(b) == 0);

上面的代码中 a.equals(b) 的结果就为 false,因为 2.00 和 2.0 小数点后的位数不同,但 a.compareTo(b) == 0 的结果就为 true,因为 2.00 和 2.0 在数学层面的值的确是相等的。

compareTo() 方法比较的过程非常严谨,源码如下:

private int compareMagnitude(BigDecimal val) {
  // Match scales, avoid unnecessary inflation
  long ys = val.intCompact;
  long xs = this.intCompact;
  if (xs == 0)
    return (ys == 0) ? 0 : -1;
  if (ys == 0)
    return 1;

  long sdiff = (long)this.scale - val.scale;
  if (sdiff != 0) {
    // Avoid matching scales if the (adjusted) exponents differ
    long xae = (long)this.precision() - this.scale;  // [-1]
    long yae = (long)val.precision() - val.scale;   // [-1]
    if (xae < yae)
      return -1;
    if (xae > yae)
      return 1;
    if (sdiff < 0) {
      // The cases sdiff <= Integer.MIN_VALUE intentionally fall through.
      if ( sdiff > Integer.MIN_VALUE &&
          (xs == INFLATED ||
              (xs = longMultiplyPowerTen(xs, (int)-sdiff)) == INFLATED) &&
          ys == INFLATED) {
        BigInteger rb = bigMultiplyPowerTen((int)-sdiff);
        return rb.compareMagnitude(val.intVal);
      }
    } else { // sdiff > 0
      // The cases sdiff > Integer.MAX_VALUE intentionally fall through.
      if ( sdiff <= Integer.MAX_VALUE &&
          (ys == INFLATED ||
              (ys = longMultiplyPowerTen(ys, (int)sdiff)) == INFLATED) &&
          xs == INFLATED) {
        BigInteger rb = val.bigMultiplyPowerTen((int)sdiff);
        return this.intVal.compareMagnitude(rb);
      }
    }
  }
  if (xs != INFLATED)
    return (ys != INFLATED) ? longCompareMagnitude(xs, ys) : -1;
  else if (ys != INFLATED)
    return 1;
  else
    return this.intVal.compareMagnitude(val.intVal);
}

接下来,用 BigDecimal 来解决开头的问题。

BigDecimal d1 = new BigDecimal("0.1");
BigDecimal three = new BigDecimal("3");
BigDecimal d2 = new BigDecimal("0.3");

d1 = d1.multiply(three);

System.out.println("d1 = " + d1);
System.out.println("d2 = " + d2);
System.out.println(d1.compareTo(d2));

程序输出的结果如下:

d1 = 0.3
d2 = 0.3
0

d1 和 d2 都为 0.3,所以 compareTo() 的结果就为 0,表示两个值是相等的。

总结一下,在遇到浮点数的时候,千万不要使用 == 操作符来进行比较,因为有精度问题。要么使用阈值来忽略舍入的问题,要么使用 BigDecimal 来替代 double 或者 float。

以上就是Java正确比较浮点数的方法的详细内容,更多关于Java 正确比较浮点数的资料请关注脚本之家其它相关文章!

相关文章

  • Java设计模式中的抽象工厂模式解读

    Java设计模式中的抽象工厂模式解读

    这篇文章主要介绍了Java设计模式中的抽象工厂模式解读,抽象工厂模式为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类,需要的朋友可以参考下
    2023-11-11
  • ArrayList源码和多线程安全问题分析

    ArrayList源码和多线程安全问题分析

    这篇文章主要介绍了ArrayList源码和多线程安全问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面小编和大家一起来学习一下吧
    2019-05-05
  • java键盘录入的方法举例详解

    java键盘录入的方法举例详解

    这篇文章主要给大家介绍了关于java键盘录入的相关资料,我们在写程序的时候,数据值都是固定的,但是实际开发中,数据值肯定是变化的,所以,把数据改进为键盘录入,提高程序的灵活性,需要的朋友可以参考下
    2023-10-10
  • Java 关于eclipse导入项目发生的问题及解决方法(推荐)

    Java 关于eclipse导入项目发生的问题及解决方法(推荐)

    下面小编就为大家分享一篇Java 关于eclipse导入项目发生的问题及解决方法(推荐),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • 详解Spring Boot 定时任务的实现方法

    详解Spring Boot 定时任务的实现方法

    最近在用SpringBoot写一个关于定时项目的时候遇到一个问题,下面小编把如何处理定时任务的解决思路分享给大家 ,需要的朋友参考下
    2017-05-05
  • 详解java封装实现Excel建表读写操作

    详解java封装实现Excel建表读写操作

    这篇文章给大家分享了java封装实现Excel建表读写操作的相关知识点内容,有需要的朋友们可以学习下。
    2018-08-08
  • SpringBoot整合MongoDB的示例

    SpringBoot整合MongoDB的示例

    这篇文章主要介绍了SpringBoot整合MongoDB的示例,帮助大家更好的理解和学习springboot框架,感兴趣的朋友可以了解下
    2020-10-10
  • Java SpringBoot项目如何优雅的实现操作日志记录

    Java SpringBoot项目如何优雅的实现操作日志记录

    这篇文章主要介绍了Java SpringBoot项目如何优雅的实现操作日志记录,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-08-08
  • icePDF去水印的方法(推荐)

    icePDF去水印的方法(推荐)

    下面小编就为大家带来一篇icePDF去水印的方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • Java 使用poi把数据库中数据导入Excel的解决方法

    Java 使用poi把数据库中数据导入Excel的解决方法

    本篇文章介绍了,Java 使用poi把数据库中数据导入Excel的解决方法。需要的朋友参考下
    2013-05-05

最新评论