CanadaHonk / proposal-math-clamp

A TC39 proposal to add Math.clamp
https://canadahonk.github.io/proposal-math-clamp/
MIT License
39 stars 1 forks source link

±0 handling #14

Open hax opened 2 days ago

hax commented 2 days ago

As the current draft:

8. If value is -0𝔽 and min is +0𝔽, return min.
9. If value is +0𝔽 and min is -0𝔽, return min.
10. If value < min, return min.
11. If value is -0𝔽 and max is +0𝔽, return max.
12. If value is +0𝔽 and max is -0𝔽, return max.

The behavior is:

Math.clamp(-0, 0, Infinity) // +0
Math.clamp(0, -0, Infinity) // -0 -- why?
Math.clamp(-0, -Infinity, 0) // +0 -- why?
Math.clamp(0, -Infinity, -0) // -0

I couldn't find the discussion about the behavior related to ±0, and I'm also curious about the rationale behind it.

BTW, I also suggest change the spec text to make the return value much clear:

8. If value is -0𝔽 and min is +0𝔽, return +0𝔽.
9. If value is +0𝔽 and min is -0𝔽, return -0𝔽.
10. If value < min, return min.
11. If value is -0𝔽 and max is +0𝔽, return +0𝔽.
12. If value is +0𝔽 and max is -0𝔽, return -0𝔽.
ljharb commented 2 days ago

I would indeed expect:

Math.clamp(-0, 0, Infinity) // +0
Math.clamp(0, -0, Infinity) // +0, because it's greater than -0
Math.clamp(-0, -Infinity, 0) // -0, because it's less than +0
Math.clamp(0, -Infinity, -0) // -0
theScottyJam commented 1 day ago

Another possible option would be to treat -0 and 0 as equal. If the value being clamped is equal to one of the bounds, prefer returning the value being clamped as opposed to the bounds.

Which means:

Math.clamp(-0, 0, Infinity) // -0, because it's the first argument
Math.clamp(0, -0, Infinity) // +0, because it's the first argument
Math.clamp(-0, -Infinity, 0) // -0, because it's the first argument
Math.clamp(0, -Infinity, -0) // -0, because it's the first argument

It also means the following, by the same logic:

Math.clamp(0, -0, -0); // +0
Math.clamp(0, 0, -0); // +0

This last one, Math.clamp(0, 0, -0), has an intuitive result under this mental model. I'm not really sure what we would want it to return if we wanted to treat -0 as pseudo-less-than +0.

ljharb commented 1 day ago

They're not equal, though - if you divide them into 1, you get opposite signed results.

theScottyJam commented 1 day ago

Depends on the definition of equals. And less than. And greater than.

I would personally expect Math.clamp()'s semantics to be fairly similar to the built-in comparison operators, < and >, which don't treat -0 as different from +0 (while the equality operator does treat them as equal). I don't think we've ever had a spot in the language that compared -0 as less than +0, and it's slightly off-putting to introduce that comparison.

That's at least my gut reaction to this. I'm going to try and see if I can come up with an actual use-case for Math.clamp() with +/-0 arguments - I might not be successful though :).

theScottyJam commented 1 day ago

Finding a real use-case for this sort of thing turns out to be really hard.

But what I did find out, was that Math.min() and Math.max() do treat -0 as less than +0. So I'm changing my mind - it makes sense to me to have Math.clamp() behave in a similar fashion to Math.min()/Math.max().

I do think it would be good to explicitly decide what the following will return:


I'd assume that we'd want this behavior:

Math.clamp(0, -0, -0); // => -0
Math.clamp(-0, 0, 0); // => 0

And for these, I have no idea:

Math.clamp(0, 0, -0); // => ??
Math.clamp(-0, 0, -0); // => ??
ljharb commented 1 day ago

providing an upper bound that's less than the lower bound throws a RangeError, so both would throw.