为什么 Math.min() 从 [+0, 0, -0] 返回 -0

Apo*_*ara 59 javascript math floating-point min negative-zero

我知道 (-0 === 0) 结果是正确的。我很好奇为什么-0 < 0 会发生?

当我在 stackoverflow 执行上下文中运行此代码时,它返回0.

const arr = [+0, 0, -0];
console.log(Math.min(...arr));
Run Code Online (Sandbox Code Playgroud)

但是当我在浏览器控制台中运行相同的代码时,它返回-0. 这是为什么?我尝试在谷歌上搜索它,但没有找到任何有用的东西。这个问题可能不会给某些实际例子增加价值,我想了解 JS 是如何计算它的。

 const arr = [+0, 0, -0];
    console.log(Math.min(...arr)); // -0
Run Code Online (Sandbox Code Playgroud)

Hol*_*olt 60

-0不小于0or +0,两者-0 < 0-0 < +0返回False,您将 的行为与/Math.min的比较混合在一起。-00+0

\n

的规范Math.min在这一点上很明确:

\n
\n

b. 如果数字为 -0 并且最低为 +0,则将最低设置为 -0。

\n
\n

Math.min如果没有这个例外,和的行为Math.max将取决于参数的顺序,这可以被认为是一种奇怪的行为 \xe2\x80\x94 您可能希望Math.min(x, y)始终等于Math.min(y, x)\xe2\x80\x94 因此这可能是一个可能的理由。

\n

注意:此异常已存在于1997 年的规范中,因此Math.min(x, y)不是后来添加的内容。

\n

  • 对我来说这似乎并不奇怪,0 是唯一一个 - 和 + 值相等的值,因此 &lt; 不足以返回一致的结果。 (15认同)
  • 我同意,但这有点奇怪,“Math.min()”的语义与“&lt;”运算符的语义不同。我想说,这绝对违反了最小意外原则。 (7认同)
  • 至于为什么总是这样实现并在 ES1 中指定:因为 [Java 具有相同的行为](/sf/ask/4718980521/),并且 `Math` 对象基本上取自 Java 。 (7认同)
  • @Pointy:如果您有一个仅定义为“a&lt;b”的 min 函数?a : b`,它也不会可靠地传播 NaN。这是 C `fmin` 和 JS `Math.min` 没有这样定义的另一个原因。(但是 C++ `std::min` 是基于 `&lt;` 的,x86 `minps` 也是如此 - 请参阅 [What is the instructions that Givebranchless FP min and max on x86?](https://stackoverflow.com/q /40196817)有关它们不可交换的详细信息。) (4认同)
  • @pilchard这是一个很好的观点,最好确保 `Math.min(x, y)` 始终等于 `Math.min(y, x)`,这样您就不必担心参数的顺序。 (3认同)
  • 但是 `-0 === 0`,所以即使我们没有这个异常,`Math.min(-0, 0) === Math.min(0, -0)` 也会成立。它的作用是保证例如`1 / Math.min(-0, 0) === 1 / Math.min(0, -0)`。但我不知道这是否是理由。 (2认同)

Jon*_*lms 12

正如所指定的Math.min,这是一个专业:

21.3.2.25 Math.min ( ...args )

[...]

  1. 对于强制的每个元素数,执行

A。如果数字为 NaN,则返回 NaN。

b. 如果数字为 -0 并且最低为 +0,则将最低设置为 -0。

C。如果数字 < 最低值,则将最低值设置为数字。

  1. 返回最低。

请注意,在大多数情况下,+0 和 -0 被同等对待,在 ToString 转换中也是如此,因此(-0).toString()计算结果为"0"。您可以在浏览器控制台中观察到的差异是浏览器的实现细节。

  • 等等,什么?JS ToString 有损并隐藏零的符号?嗯…… (8认同)
  • @leo848 `Object.is(-0, theValue)` ...大多数其他比较(包括`===`)将它们视为相等。 (2认同)

Pet*_*des 9

这个答案的要点是解释为什么语言设计选择Math.min完全可交换是有意义的。

我很好奇为什么-0 < 0 会发生?

事实并非如此;<是与“最小值”分开的操作,并且Math.min不仅仅基于 IEEE<比较(如b<a ? b : a.

这将是不可交换的。NaN 以及有符号零。(<如果任一操作数为 NaN,则为 false,因此会产生a)。就最小惊奇原则而言,如果是
,那么至少同样令人惊讶(如果不是更令人惊讶的话)。Math.min(-1,NaN)NaNMath.min(NaN, -1)-1

JS 语言设计者想要Math.minNaN 传播,所以仅仅基于它<是不可能的。 他们选择使其完全可交换,包括签名零,这似乎是一个明智的决定。

OTOH,大多数代码并不关心带符号的零,因此这种语言设计选择会牺牲每个人的性能,以满足某些人想要明确定义的带符号零语义的罕见情况。

如果您想要一个忽略数组中 NaN 的简单操作,请使用 进行迭代current_min = x < current_min ? x : current_min。这将忽略所有 NaN,也忽略-0for current_min <= +0.0(IEEE 比较)。或者,如果current_min开始为 NaN,它将保持 NaN。其中许多事情对于Math.min函数来说都是不受欢迎的,所以它不会那样工作。


如果比较其他语言C 标准fmin函数是可交换的。NaN(如果有则返回非 NaN,与 JS 相反),但不要求是可交换的。签名为零。一些 C 实现选择像 JS 一样工作 +-0.0 for fmin/ fmax

C++纯粹std::min 根据<操作来定义的,所以它确实这样工作的。(它旨在一般情况下工作,包括字符串等非数字类型;不同之处在于std::fmin它没有任何特定于 FP 的规则。)请参阅在 x86 上提供无分支 FP 最小值和最大值的指令是什么?回复:x86 的minps指令和 C++std::min都是不可交换的。NaN 和有符号零。


IEEE 754<不会为您提供不同 FP 编号的全序。Math.min除了 NaN 之外(例如,如果您用它构建了一个排序网络,并且Math.max。)它的顺序与 不同Math.max:如果存在,它们都返回 NaN,因此使用最小/最大比较器的排序网络将产生所有 NaN(如果存在)输入数组。

Math.min如果没有像查看它返回哪个参数这样的东西,仅靠排序是不够的==,但是对于有符号零和 NaN 来说,这会崩溃。