双包装器 class 的 .equals() 方法是否适用于查找浮点数的相等性?
Does the .equals() method of the double wrapper class work for finding equality of floating point numbers?
我知道对于原始浮点类型(浮点数和双精度数),您不应该通过 ==
直接比较它们。但是,如果您将包装器 class 用于 double 怎么办?会不会像
Double a = 5.05;
Double b = 5.05;
boolean test = a.equals(b);
正确比较两个值?
您需要完全理解为什么 ==
比较不是一个好主意。不理解,就是在黑暗中摸索。
让我们谈谈计算机(和双打)的工作原理。
假设你进入了一个房间;它有 3 个电灯开关,否则它是光秃秃的。你会进入房间,你可以 fiddle 使用开关,但你必须离开。我稍后进入房间,可以看看开关。
你能传达多少信息?
答案是:你可以传达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
飞镖。他们登陆的数字是 'blessed numbers' - 这些由 double
值表示。这确实意味着任意 2 个飞镖之间有无限数量的数字,因此 不能 表示。飞镖不是很随机:越接近 0,飞镖越密集。一旦超过 2^52
左右,2 镖之间的距离甚至超过 1.0。
这是一个无法表示的数字的简单示例:0.3。太棒了,不是吗?就这么简单。这意味着 计算机实际上无法使用 double
计算 0.1 + 0.2。那么当你尝试时会发生什么?规则规定任何计算的结果总是四舍五入到最接近的祝福数字。
问题就在这里:你可以 运行 数学:
double x = 0.1 + 0.2;
以后做:
double y = 0.9 - 0.8 + 0.15 + 0.05;
而我们人类会立即注意到 x
和 y
自然是相同的。但是 对于计算机来说并非如此 - 由于无声四舍五入到最接近的祝福数字,x
可能是 0.29999999999999999785
,而 y
是 0.300000000000000000012
.
因此我们在使用 double
(或 float
时有四个关键方面,这在所有方面都差不多,永远不要使用它们):
- 如果您需要绝对精度,请完全不要使用它们。
- 打印时,始终向下舍入。 System.out.println 开箱即用,但你真的应该使用
.printf("%.5f")
或类似的:选择你需要的数字。
- 请注意,错误会加剧,并且随着您离 1.0 越来越远,情况会变得更糟。
- 永远不要与
==
进行比较,而是始终使用增量比较:“如果 2 个数字彼此相差在 0.0000000001 以内,则我们认为它们相等”的概念。
没有通用的魔法增量值;这取决于你的精度需求,你离 1.0 有多远,等等。因此,只要问计算机:嘿,把这些东西弄清楚我只想知道这两个双打是否相等 不可能.关于 'how close' 它们可能是什么,唯一不需要您输入的可用定义是绝对完美的概念:仅当它们完全相同时它们才相等。在上面那个简单的例子中,这个定义会让你失败。如果您使用 Double.equals
而不是 double == double
或任何其他实用程序 class,这不会有任何区别。
所以,不,Double.equals
不合适。您将不得不比较 Math.abs(d1 - d2) < epsilon
,其中 epsilon 是您的选择。大多数情况下,如果平等很重要,那么您已经做错了,一开始就不应该使用 double
。
注意:在表示金钱时,您不希望四舍五入,因此切勿对这些使用双精度值。相反,弄清楚原子银行单位是什么(美元、欧分、日元、比特币的 satoshis 等),并将其存储为 long。您存储 $4.52 和 long x = 452;
,而不是 double x = 4.52;
.
我知道对于原始浮点类型(浮点数和双精度数),您不应该通过 ==
直接比较它们。但是,如果您将包装器 class 用于 double 怎么办?会不会像
Double a = 5.05;
Double b = 5.05;
boolean test = a.equals(b);
正确比较两个值?
您需要完全理解为什么 ==
比较不是一个好主意。不理解,就是在黑暗中摸索。
让我们谈谈计算机(和双打)的工作原理。
假设你进入了一个房间;它有 3 个电灯开关,否则它是光秃秃的。你会进入房间,你可以 fiddle 使用开关,但你必须离开。我稍后进入房间,可以看看开关。
你能传达多少信息?
答案是:你可以传达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
飞镖。他们登陆的数字是 'blessed numbers' - 这些由 double
值表示。这确实意味着任意 2 个飞镖之间有无限数量的数字,因此 不能 表示。飞镖不是很随机:越接近 0,飞镖越密集。一旦超过 2^52
左右,2 镖之间的距离甚至超过 1.0。
这是一个无法表示的数字的简单示例:0.3。太棒了,不是吗?就这么简单。这意味着 计算机实际上无法使用 double
计算 0.1 + 0.2。那么当你尝试时会发生什么?规则规定任何计算的结果总是四舍五入到最接近的祝福数字。
问题就在这里:你可以 运行 数学:
double x = 0.1 + 0.2;
以后做:
double y = 0.9 - 0.8 + 0.15 + 0.05;
而我们人类会立即注意到 x
和 y
自然是相同的。但是 对于计算机来说并非如此 - 由于无声四舍五入到最接近的祝福数字,x
可能是 0.29999999999999999785
,而 y
是 0.300000000000000000012
.
因此我们在使用 double
(或 float
时有四个关键方面,这在所有方面都差不多,永远不要使用它们):
- 如果您需要绝对精度,请完全不要使用它们。
- 打印时,始终向下舍入。 System.out.println 开箱即用,但你真的应该使用
.printf("%.5f")
或类似的:选择你需要的数字。 - 请注意,错误会加剧,并且随着您离 1.0 越来越远,情况会变得更糟。
- 永远不要与
==
进行比较,而是始终使用增量比较:“如果 2 个数字彼此相差在 0.0000000001 以内,则我们认为它们相等”的概念。
没有通用的魔法增量值;这取决于你的精度需求,你离 1.0 有多远,等等。因此,只要问计算机:嘿,把这些东西弄清楚我只想知道这两个双打是否相等 不可能.关于 'how close' 它们可能是什么,唯一不需要您输入的可用定义是绝对完美的概念:仅当它们完全相同时它们才相等。在上面那个简单的例子中,这个定义会让你失败。如果您使用 Double.equals
而不是 double == double
或任何其他实用程序 class,这不会有任何区别。
所以,不,Double.equals
不合适。您将不得不比较 Math.abs(d1 - d2) < epsilon
,其中 epsilon 是您的选择。大多数情况下,如果平等很重要,那么您已经做错了,一开始就不应该使用 double
。
注意:在表示金钱时,您不希望四舍五入,因此切勿对这些使用双精度值。相反,弄清楚原子银行单位是什么(美元、欧分、日元、比特币的 satoshis 等),并将其存储为 long。您存储 $4.52 和 long x = 452;
,而不是 double x = 4.52;
.