首页 > 解决方案 > 双包装类的 .equals() 方法是否可用于查找浮点数的相等性?

问题描述

我知道对于原始浮点类型(浮点数和双精度数),您不应该直接通过==. 但是如果你使用双倍的包装类呢?会像

Double a = 5.05;
Double b = 5.05; 
boolean test = a.equals(b);

正确比较这两个值?

标签: javafloating-point

解决方案


您需要完全理解为什么== 比较是一个坏主意的原因。没有理解,你只是在黑暗中摸索。

让我们谈谈计算机(和替身)是如何工作的。

想象你进入一个房间;它有3个灯开关,否则它是光秃秃的。你将进入房间,你可以摆弄开关,但你必须离开。我稍后进入房间,可以查看开关。

你能传达多少信息?

答案是:您可以传达 8 种不同的状态:DDD、DDU、DUD、DUU、UDD、UDU、UUD 和 UUU。就是这样。

当计算机存储双精度时,计算机的工作方式与此完全相同。除了 3 个开关,你得到 64 个开关。这意味着2^64您可以使用单个 来传达不同的状态double,这是大量的状态:这是一个 19 位数字。

但它仍然是有限数量的状态,这是有问题的:0到1之间的数字是无限的。更不用说-infinity和+infinity之间了,它double敢于覆盖。当你只代表状态时,你如何存储无限的选择之一2^64?当这个 19 位数字被要求从无限的可能性中区分出来时,它开始看起来非常小,不是吗?

答案当然是这完全不可能

所以双打实际上并不是这样工作的。取而代之的是,有人费了些力气,在一个大房间里挂了一条巨大的数字线,从负无穷到正无穷,向2^64这条线投掷飞镖。他们登陆的数字是“有福的数字”——这些数字可以用一个double值来表示。这确实意味着任何两个飞镖之间都有无限数量的数字,因此无法表示。飞镖不是很随机:越接近 0,飞镖越密集。一旦超过大约2^522 支飞镖之间的距离甚至超过 1.0。

这是一个不可表示的数字的简单示例:0.3。很神奇,不是吗?这么简单的事情。这意味着计算机实际上无法使用double. 那么当你尝试时会发生什么?规则规定,任何计算的结果总是默默地四舍五入到最接近的祝福数字。

这就是问题所在:您可以运行数学:

double x = 0.1 + 0.2;

然后做:

double y = 0.9 - 0.8 + 0.15 + 0.05;

我们人类会立即注意到这一点x,并且y自然是相同的。但对于计算机而言并非如此——因为无声四舍五入到最接近的祝福数字,它可能x0.29999999999999999785,并且y0.300000000000000000012

因此,我们在使用时会遇到四个关键方面double(或者float在每种方式中都更糟,永远不要使用这些方面):

  • 如果您需要绝对精度,请不要使用它们。
  • 打印它们时,请始终将它们四舍五入。System.out.println 开箱即用,但您应该真正使用.printf("%.5f")或类似:选择您需要的数字#。
  • 请注意,错误会更加复杂,并且随着您离 1.0 越来越远,它会变得更糟。
  • 永远不要与 进行比较==,而是始终使用 delta-compare:“如果两个数字在彼此的 0.0000000001 范围内,则让我们认为它们相等”的概念。

没有通用的魔法增量值;这取决于您的精度需求,您距离 1.0 的距离等。因此,只需询问计算机:嘿,弄清楚这些东西我只想知道这两个双打是否相等是不可能的。唯一不需要您输入它们“有多接近”的可用定义是绝对完美的概念:只有当它们完全相同时它们才相等。在上面那个简单的例子中,这个定义会让你失望。如果您使用Double.equalsinstead ofdouble == double或任何其他实用程序类,它不会有任何不同。

所以,不,Double.equals不适合。您将不得不比较Math.abs(d1 - d2) < epsilon,其中 epsilon 是您的选择。大多数情况下,如果平等很重要,那么您已经做错了,并且不应该首先使用double

NB:当代表金钱时,你不想要不可预测的四舍五入,所以永远不要使用双精度数。相反,找出原子银行单位是什么(美元、欧分、日元、比特币的聪等),并将其存储为 long。您存储 4.52 美元long x = 452;,而不是double x = 4.52;.


推荐阅读