为什么 JavaScript 中的“(2.5 < 2.5 + Number.EPSILON)”为假?
Why is "(2.5 < 2.5 + Number.EPSILON)" false in JavaScript?
我想在数组中查找小于特定值的值。
我尝试使用 Number.EPSILON 因为输入值不是确定值(例如 1.5000000000001)。
我在测试中发现了一些奇怪的东西:
>> (1.5 < 1.5 + Number.EPSILON)
<- true
>> (2.5 < 2.5 + Number.EPSILON)
<- false
这是为什么?
测试环境是Chrome浏览器控制台。
虽然 Number.EPSILON
本身可以精确表示,但这并不能保证向其添加值(或对其进行任何进一步操作)会产生完美的精度。在这种情况下,1.5 + Number.EPSILON
导致数字略高于 1.5:
console.log(1.5 + Number.EPSILON);
明显大于1.5。另一方面,将 2.5 添加到 Number.EPSILON
结果正好是 2.5 - 您希望的精度在加法过程中丢失了。
console.log(2.5 + Number.EPSILON);
正如预期的那样,2.5 < 2.5
的计算结果为 false
。
Number.EPSILON 是:
difference between 1 and the smallest floating point number greater
than 1
为了论证起见,假设这个数字是 0.00000000000000000000000001。
现在,
1.5 < 1.5 + 0.00000000000000000000000001 === true
而且,当你加上这个极小的分数的基数变大时,JS数学评估的精度计算就找到了它的边界。
2 < 2 + 0.00000000000000000000000001 === false
浮点数的精度有限。根据语言和体系结构,它们通常使用 32 位(float
)或 64 位(double
,从 "double precision" 开始)表示。尽管在 JavaScript 这样的无类型语言中事情变得模糊,但在这一切之下仍然有一台实际的机器,并且这台机器必须执行浮点运算。
问题在于,由于精度有限,某些计算结果无法准确表示。在 Wikipedia page about floating point artithmetic 上用一些例子解释了这一点。
对于想要所有 nitty-gritty 详细信息的人,通常推荐有关 What Every Computer Scientist Should Know About Floating-Point Arithmetic 的文章。但说真的:并不是每个计算机科学家都需要知道所有这些,而且我很确定世界上只有少数人真正 阅读 整件事....
作为一个过于暗示性的例子:假设你有 5 位数字来存储一个数字。当您添加
之类的内容时
10000.
+ 0.00001
--------------------
= 10000.
.00001
部分基本上是 "truncated" 因为它不适合 5 位数字。
(这并不是它的工作原理,但应该能理解这个想法)
Number.EPSILON
的实际值,根据 the documentation, is approximately 2.22 * 10-16, and is the "difference between 1 and the smallest floating point number greater than 1". (This is sometimes referred to as an ULP, Unit In The Last Place)。
因此将此值添加到 1.0 将导致不同的数字。但是加上2.5会not得到不同的数,因为2.5和大于2.5的最小浮点数的差比大这个小量。因此,epsilon 被截断,如上例中的 .00001
。
一些 languages/libraries 可能会提供一个 "ulp" 函数,该函数 returns 给定值与下一个更大的可表示值之间的差异。例如,在 Java 中,您有
System.out.println(Math.ulp(1.0)); // Prints 2.220446049250313E-16
System.out.println(Math.ulp(2.5)); // Prints 4.440892098500626E-16
第一个显然是存储在Number.EPSILON
中的内容。第二个是添加到 2.5 时应该产生不同值的值。所以
2.5 < 2.5 + 4.4408E-16
将是 false
和
2.5 < 2.5 + 4.4409E-16
将是 true
/*
┌─────────────────────────────────────┐
│ Number.EPSILON = 2¯⁵² ≈ 2.2 * 10¯¹⁶ │
└─────────────────────────────────────┘
Question 1: 2.5 < 2.5 + ε ?
2¯⁵¹
2¹↴ ⇩
10100000000000000000000000000000000000000000000000000 = 2.5 (JS)
+ 1 = ε
─────────────────────────────────────────────────────────────────────
┌┐ (01, 1 truncated )
101000000000000000000000000000000000000000000000000001
= 10100000000000000000000000000000000000000000000000000 = 2.5 (JS)
╰──────────────────── 52-bit ──────────────────────╯
⭐️ Conclusion ❌: 2.5 === 2.5 + ε
Question 2: 1.5 < 1.5 + ε ?
2¯⁵²
2⁰↴ ⇩
11000000000000000000000000000000000000000000000000000 = 1.5 (JS)
+ 1 = ε
─────────────────────────────────────────────────────────────────────
= 11000000000000000000000000000000000000000000000000001 = 1.5 + ε (JS)
╰──────────────────── 52-bit ──────────────────────╯
⭐️ Conclusion ✅: 1.5 < 1.5 + ε
*/
// --------------- log ---------------
const ε = Number.EPSILON;
[
2.5 < 2.5 + ε, // false❗️
1.5 < 1.5 + ε, // true
].forEach(x => console.log(x))
我想在数组中查找小于特定值的值。 我尝试使用 Number.EPSILON 因为输入值不是确定值(例如 1.5000000000001)。
我在测试中发现了一些奇怪的东西:
>> (1.5 < 1.5 + Number.EPSILON)
<- true
>> (2.5 < 2.5 + Number.EPSILON)
<- false
这是为什么? 测试环境是Chrome浏览器控制台。
虽然 Number.EPSILON
本身可以精确表示,但这并不能保证向其添加值(或对其进行任何进一步操作)会产生完美的精度。在这种情况下,1.5 + Number.EPSILON
导致数字略高于 1.5:
console.log(1.5 + Number.EPSILON);
明显大于1.5。另一方面,将 2.5 添加到 Number.EPSILON
结果正好是 2.5 - 您希望的精度在加法过程中丢失了。
console.log(2.5 + Number.EPSILON);
2.5 < 2.5
的计算结果为 false
。
Number.EPSILON 是:
difference between 1 and the smallest floating point number greater than 1
为了论证起见,假设这个数字是 0.00000000000000000000000001。
现在,
1.5 < 1.5 + 0.00000000000000000000000001 === true
而且,当你加上这个极小的分数的基数变大时,JS数学评估的精度计算就找到了它的边界。
2 < 2 + 0.00000000000000000000000001 === false
浮点数的精度有限。根据语言和体系结构,它们通常使用 32 位(float
)或 64 位(double
,从 "double precision" 开始)表示。尽管在 JavaScript 这样的无类型语言中事情变得模糊,但在这一切之下仍然有一台实际的机器,并且这台机器必须执行浮点运算。
问题在于,由于精度有限,某些计算结果无法准确表示。在 Wikipedia page about floating point artithmetic 上用一些例子解释了这一点。
对于想要所有 nitty-gritty 详细信息的人,通常推荐有关 What Every Computer Scientist Should Know About Floating-Point Arithmetic 的文章。但说真的:并不是每个计算机科学家都需要知道所有这些,而且我很确定世界上只有少数人真正 阅读 整件事....
作为一个过于暗示性的例子:假设你有 5 位数字来存储一个数字。当您添加
之类的内容时 10000.
+ 0.00001
--------------------
= 10000.
.00001
部分基本上是 "truncated" 因为它不适合 5 位数字。
(这并不是它的工作原理,但应该能理解这个想法)
Number.EPSILON
的实际值,根据 the documentation, is approximately 2.22 * 10-16, and is the "difference between 1 and the smallest floating point number greater than 1". (This is sometimes referred to as an ULP, Unit In The Last Place)。
因此将此值添加到 1.0 将导致不同的数字。但是加上2.5会not得到不同的数,因为2.5和大于2.5的最小浮点数的差比大这个小量。因此,epsilon 被截断,如上例中的 .00001
。
一些 languages/libraries 可能会提供一个 "ulp" 函数,该函数 returns 给定值与下一个更大的可表示值之间的差异。例如,在 Java 中,您有
System.out.println(Math.ulp(1.0)); // Prints 2.220446049250313E-16
System.out.println(Math.ulp(2.5)); // Prints 4.440892098500626E-16
第一个显然是存储在Number.EPSILON
中的内容。第二个是添加到 2.5 时应该产生不同值的值。所以
2.5 < 2.5 + 4.4408E-16
将是false
和2.5 < 2.5 + 4.4409E-16
将是true
/*
┌─────────────────────────────────────┐
│ Number.EPSILON = 2¯⁵² ≈ 2.2 * 10¯¹⁶ │
└─────────────────────────────────────┘
Question 1: 2.5 < 2.5 + ε ?
2¯⁵¹
2¹↴ ⇩
10100000000000000000000000000000000000000000000000000 = 2.5 (JS)
+ 1 = ε
─────────────────────────────────────────────────────────────────────
┌┐ (01, 1 truncated )
101000000000000000000000000000000000000000000000000001
= 10100000000000000000000000000000000000000000000000000 = 2.5 (JS)
╰──────────────────── 52-bit ──────────────────────╯
⭐️ Conclusion ❌: 2.5 === 2.5 + ε
Question 2: 1.5 < 1.5 + ε ?
2¯⁵²
2⁰↴ ⇩
11000000000000000000000000000000000000000000000000000 = 1.5 (JS)
+ 1 = ε
─────────────────────────────────────────────────────────────────────
= 11000000000000000000000000000000000000000000000000001 = 1.5 + ε (JS)
╰──────────────────── 52-bit ──────────────────────╯
⭐️ Conclusion ✅: 1.5 < 1.5 + ε
*/
// --------------- log ---------------
const ε = Number.EPSILON;
[
2.5 < 2.5 + ε, // false❗️
1.5 < 1.5 + ε, // true
].forEach(x => console.log(x))