Why does Math.min() return -0 from [+0, 0, -0]

JavascriptMathFloating PointMinNegative Zero

Javascript Problem Overview


I know (-0 === 0) comes out to be true. I am curious to know why -0 < 0 happens?

When I run this code in stackoverflow execution context, it returns 0.

const arr = [+0, 0, -0];
console.log(Math.min(...arr));

But when I run the same code in the browser console, it returns -0. Why is that? I have tried to search it on google but didn't find anything useful. This question might not add value to someone practical example, I wanted to understand how does JS calculates it.

 const arr = [+0, 0, -0];
    console.log(Math.min(...arr)); // -0

Javascript Solutions


Solution 1 - Javascript

-0 is not less than 0 or +0, both -0 < 0 and -0 < +0 returns False, you're mixing the behavior of Math.min with the comparison of -0 with 0/+0.

The specification of Math.min is clear on this point:

> b. If number is -0픽 and lowest is +0픽, set lowest to -0픽.

Without this exception, the behavior of Math.min and Math.max would depend on the order of arguments, which can be considered an odd behavior — you probably want Math.min(x, y) to always equal Math.min(y, x) — so that might be one possible justification.

Note: This exception was already present in the 1997 specification for Math.min(x, y), so that's not something that was added later on.

Solution 2 - Javascript

This is a specialty of Math.min, as specified:

> 21.3.2.25 Math.min ( ...args ) > > [...] > > 4. For each element number of coerced, do > > a. If number is NaN, return NaN. > > b. If number is -0픽 and lowest is +0픽, set lowest to -0픽. > > c. If number < lowest, set lowest to number. > > 5. Return lowest.

Note that in most cases, +0 and -0 are treated equally, also in the ToString conversion, thus (-0).toString() evaluates to "0". That you can observe the difference in the browser console is an implementation detail of the browser.

Solution 3 - Javascript

The point of this answer is to explain why the language design choice of having Math.min be fully commutative makes sense.

> I am curious to know why -0 < 0 happens?

It doesn't really; < is a separate operation from "minimum", and Math.min isn't based solely on IEEE < comparison like b<a ? b : a.

That would be non-commutative wrt. NaN as well as signed-zero. (< is false if either operand is NaN, so that would produce a).
As far as principle of least surprise, it would be at least as surprising (if not moreso) if Math.min(-1,NaN) was NaN but Math.min(NaN, -1) was -1.

The JS language designers wanted Math.min to be NaN-propagating, so basing it just on < wasn't possible anyway. They chose to make it fully commutative including for signed zero, which seems like a sensible decision.

OTOH, most code doesn't care about signed zero, so this language design choice costs a bit of performance for everyone to cater to the rare cases where someone wants well-defined signed-zero semantics.

If you want a simple operation that ignores NaN in an array, iterate yourself with current_min = x < current_min ? x : current_min. That will ignore all NaN, and also ignore -0 for current_min <= +0.0 (IEEE comparison). Or if current_min starts out NaN, it will stay NaN. Many of those things are undesirable for a Math.min function, so it doesn't work that way.


If you compare other languages, the C standard fmin function is commutative wrt. NaN (returning the non-NaN if there is one, opposite of JS), but is not required to be commutative wrt. signed zero. Some C implementations choose to work like JS for +-0.0 for fmin / fmax.

But C++ std::min is defined purely in terms of a < operation, so it does work that way. (It's intended to work generically, including on non-numeric types like strings; unlike std::fmin it doesn't have any FP-specific rules.) See https://stackoverflow.com/questions/40196817/what-is-the-instruction-that-gives-branchless-fp-min-and-max-on-x86 re: x86's minps instruction and C++ std::min which are both non-commutative wrt. NaN and signed zero.


IEEE 754 < doesn't give you a total order over distinct FP numbers. Math.min does except for NaNs (e.g. if you built a sorting network with it and Math.max.) Its order disagrees with Math.max: they both return NaN if there is one, so a sorting network using min/max comparators would produce all NaNs if there were any in the input array.

Math.min alone wouldn't be sufficient for sorting without something like == to see which arg it returned, but that breaks down for signed zero as well as NaN.

Solution 4 - Javascript

The spec is curiously contradictory. The < comparison rule explicitly says that -0 is not less than +0. However, the spec for Math.min() says the opposite: if the current (while iterating through the arguments) value is -0, and the smallest value so far is +0, then the smallest value should be set to -0.

I would like for somebody to activate the T.J. Crowder signal for this one.

edit — it was suggested in some comments that a possible reason for the behavior is to make it possible to detect a -0 value, even though for almost all purposes in normal expressions the -0 is treated as being plain 0.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionApoorva ChikaraView Question on Stackoverflow
Solution 1 - JavascriptHoltView Answer on Stackoverflow
Solution 2 - JavascriptJonas WilmsView Answer on Stackoverflow
Solution 3 - JavascriptPeter CordesView Answer on Stackoverflow
Solution 4 - JavascriptPointyView Answer on Stackoverflow