-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How should Math.min/max(0, 0n) be ordered? #3
Comments
Another option is just to have |
But this means we should add |
I think the committee would probably push back against adding a new global. They would probably rather add methods to I will raise the possibility of using |
|
While this would probably be ideal, there’s currently very little rhyme or reason to how Number operations and constants are distributed between Number and Math and it might be sufficient to say “new stuff follows this pattern” or “new stuff for numeric types other than Number follow this pattern” without worrying too much about Math. |
After the TC39 meeting, I currently plan to extend |
As in "first wins when a Number and BigInt have equivalent mathematical values", or as in "Number wins"? Is |
"first wins" is one option, but another option might be "always prefer bigint" or "always prefer number" - iow, |
Maybe we should match Actually…in order to really match > [1, 0].sort()
[ 0, 1 ]
> Math.max(...[1, 0])
1
> Math.min(...[1, 0])
0
> [0, 0n].sort()
[ 0, 0n ]
> Math.max(...[0, 0n])
0n
> Math.min(...[0, 0n])
0
> [0n, 0].sort()
[ 0n, 0 ]
> Math.max(...[0n, 0])
0
> Math.min(...[0n, 0])
0n |
@js-choi since it's a list of arguments instead of an array, i don't think that's observable either way, so i think we're free to make a choice here (since it's just about fictional mental models) |
Can we please not bring up [10n, 2].sort()
> [10n, 2]
[10n, 200].sort()
> [10n, 200] |
@sarahghp i 100% agree that mental models are important; i'm just not convinced that the majority of users have a mental model about ordering in Math.max/min arguments. |
Is there a reason not to make it work like:
Because my initial instinct is that's the parallel that makes sense. What is max and min but sorting and shifting or popping? |
@sarahghp, @michaelficarra, @ljharb: Yeah, I was sloppy. What I had originally meant was to propose to match Edit: Still-incorrect, sleep-deprived code> [1, 0].sort((a, b) => a < b)
[ 0, 1 ]
> Math.max(...[1, 0])
1
> Math.min(...[1, 0])
0
> [0, 0n].sort((a, b) => a < b)
[ 0, 0n ]
> Math.max(...[0, 0n])
0n
> Math.min(...[0, 0n])
0
> [0n, 0].sort((a, b) => a < b)
[ 0n, 0 ]
> Math.max(...[0n, 0])
0
> Math.min(...[0n, 0])
0n |
@michaelficarra If the default function works on strings and @js-choi and I both default to simple comparison, then it can be argued that's how comparison functions work. But honestly, as long as we are basing it on behavior that exists and is explainable, I think either approach is reasonable. This also might be nice to do lightweight (Twitter poll) research on, to see if there is an obvious expectation. |
@michaelficarra: Argh, sorry, I’m really sleep deprived right now. Yes. |
Okay, now that I’ve paid back my sleep debt, I should be able to think about this more coherently. There are three valid developer mental models I see: a reduce model, a sort model, and a tower model. Reduce model“Min and max are what I’d get when I reduce the list using I’ve implemented When I reduce, I tend to prefer reducing to the last value, rather than the first. So, with the reduce model, I personally expect > [ 0, 0n ].reduce((a, b) => a <= b ? a : b)
0
> [ 0n, 0 ].reduce((a, b) => a <= b ? a : b)
0n …and > [ 0, 0n ].reduce((a, b) => a <= b ? b : a)
0n
> [ 0n, 0 ].reduce((a, b) => a <= b ? b : a)
0 However: changing > [ 0, 0n ].reduce((a, b) => a < b ? b : a) // First example, but the <= is changed to <.
0 Sort model“Min and max are the leftmost and rightmost values of the sorted array.” We also may conceptually think of We generally expect > [ 0, 0n ].sort((a, b) => a < b ? -1 : a == b ? 0 : +1)
[ 0, 0n ]
> [ 0, 0n ].sort((a, b) => a < b ? -1 : a == b ? 0 : +1)
[ 0n, 0 ] …and, in this model, the first sorted value is the minimum and the last sorted value is the maximum. Tower model“There’s an intrinsic ordering to numeric types, and integer types come before equal non-integer types, and min and max use this type order.” From what I recall, languages that have a numeric tower generally define a total order over all their numeric values. In this case, sorting a list with an exact integer and an equivalent non-integer-type number would always sort the integer before the fractional number. However: JavaScript does not have a numeric tower and it has no total order over its numerics; its BigInts and Numbers are orthogonal in every way. If we elect to base ConclusionEach model has its own reasons to be considered arbitrary, so the whole choice could be considered arbitrary. So we can bikeshed this for as long as we need. For now, my plan for the spec is to prefer the earliest equivalent value for |
Taking a step back here, would someone be able to shed some light on anticipated use cases? If we imagine code like:
Then firstly that raises the question: where would And secondly this code, as written, wouldn't generally work, because I'm not debating that there are several valid (though arbitrary) ways to spec what |
I have no idea what I’d use the midpoint for - when i max and min things it’s to then take it and use it directly, often to render it in a UI, or iterate from or to it. |
Yes, I think the usefulness of |
That's actually one of my more common use cases - i have more than 2 items, and I don't want to hardcode a bunch of |
Yes, and to elaborate further on where those mixes might come from, I mostly imagine myself winding up here on the edges of joining different systems — using a new library with legacy code, getting values from various APIs, etc. In terms of doing further arithmetic, even if it can't be performed on mixed values, reducing a list to a single value before converting instead of converting the entire list has its advantages. (I would argue it would be nice to be able to do the arithmetic and implicitly convert to BigInt, but I know that's not a popular POV these days. 😆 ) Anyways, this is not my proposal — I'm just some girl with an opinion who helped on the docs once — so I will stop posting so much, but that's basically how and why I can see this being useful and why I was surprised it did not already exist. |
Personally I prefer the reduce model, and whether first or last depends on use case; I'd probably expect first more often. The sort model feels weird to me, can't explain why, However, due to the weird handling of
|
I presented a brief update presentation about this issue to the Committee at the October plenary today. I presented the four potential mental models we could go with: the two reduce models, the sort model, and the tower model. I didn’t get any signals over this issue, although feedback time was brief. I tentatively plan to move forward with the sort model when I present this proposal again for Stage 2 in several months. |
What if we try to preserve precision as much as possible? What I mean is that both typeof Math.max(2 ** 52, 1n << 52n) // 'number'
typeof Math.max(2 ** 53, 1n << 53n) // 'bigint'
// this also applies to "unsafe" negatives This has the advantage that further operations on the return values are fast (when possible) and accurate (when possible), depending on the input args |
@Rudxain the committee explicitly decided to avoid having behavior change based on the "safeness" of the number, during the bigint proposal, so i don't think that would gain consensus. |
Oh... Then I would "vote" for choosing something similar to the reduce model. But I prefer that Math.max(1n, 1) === Math.min(1n, 1) So if we choose "left 1st" or "right 1st" for one function, the other one should do the same. This is easier to remember, and less prone to bugs, because there's no way to confuse them |
They have different behavior with a single argument already because they're "opposite" operations, I wouldn't expect them to have the same ordering behaviors (assuming order matters), |
They have different behavior with 0 args, not 1, but that's still a good point. "Asymmetric" definitions can be useful, a good example is the standard modulo operation which is defined in terms of floor division, this has useful properties like |
The reduce model explains const max = (...args) => args.reduce((acc, cur) => cur > acc ? cur : acc, -Infinity); (This is how it's typically implemented anyway, I think; also how it's defined in the spec) |
@Josh-Cena: I agree that the reduce models are intuitive, but there are two reduce models: one using < / > and one using ≤ / ≥. const max = (...args) => args.reduce((acc, cur) => cur > acc ? cur : acc, -Infinity);
const max = (...args) => args.reduce((acc, cur) => cur >= acc ? cur : acc, -Infinity);
|
Ahhh, because the current spec uses < / >, is there a strong enough argument for ≤ / ≥ to warrant a change? 😄 Otherwise I have to say I favor sticking with the existing semantic (although I believe it's mostly arbitrarily chosen). If I have to write actual code, I'd also be leaning towards < / >: // Some actual code I've written when doing competitive programming
int highest = -2147483648;
for (int i = 0; i < N; i++) {
// Using > instead of >= will write to highest a few times less;
// Who doesn't like micro-optimizations?
if (val[i] > highest) highest = val[i];
} But yeah, either would seem arbitrary and leak abstraction. |
I suggest to add those methods to
globalThis.BigIntMath.*
so we can get rid ofbigMin
andbigMax
.And please consider the Decimal proposal, if we follow this naming approach, will we have
decimalMin
anddecimalMax
?The text was updated successfully, but these errors were encountered: