tc39 / proposal-intl-numberformat-v3

Additional features for Intl.NumberFormat to solve key pain points.
MIT License
53 stars 12 forks source link

What rounding modes to include? #7

Closed sffc closed 3 years ago

sffc commented 4 years ago

ICU has the following options:

  1. "halfUp" (default)
  2. "halfEven"
  3. "halfDown"
  4. "ceiling"
  5. "floor"
  6. "up"
  7. "down"

Chart: http://userguide.icu-project.org/formatparse/numbers/rounding-modes

Do we want to include all those in Intl.NumberFormat? How would we pick a subset?

@waldemarhorwat @littledan

erights commented 4 years ago

Is halfUp the same as "round to even"?

sffc commented 4 years ago

Is halfUp the same as "round to even"?

I think that would be halfEven. See the chart: http://userguide.icu-project.org/formatparse/numbers/rounding-modes

littledan commented 4 years ago

FWIW Java BigDecimal has basically the same set of modes, plus an UNNECESSARY mode which throws if rounding was needed. I don't know whether this was an independent invention, though, given that both of these projects had strong IBM involvement/authorship.

I suspect most of these are needed. The only two I'm suspicious of are floor and ceiling; I wonder if up and down would be enough (or, if floor/ceiling would be enough, without up/down). I'd like to collect more data somehow.

I tried to ask about rounding modes in the decimal survey but I haven't gotten any answers yet that really got at the semantics needed for negative numbers. I suspect negative numbers are important in some accounting/money use cases, though. Maybe this is something to follow up on in further interviews.

waldemarhorwat commented 4 years ago

Is halfUp the same as "round to even"?

No. halfUp will round ±0.5 to ±1, while halfEven will round ±0.5 to ±0.

The default internal IEEE arithmetic rounding is halfEven. The default rounding the IRS asks you to do on taxes is halfUp.

The set of modes here seems like one of the standard rounding repertoires and seems fine to me.

sffc commented 4 years ago

We should align the Intl rounding modes with the Decimal rounding modes. Perhaps this is a decision best made in the Decimal proposal, and the roundingMode option for 402 could piggy-back on that proposal instead of being in this proposal.

waldemarhorwat commented 4 years ago

@sffc: The Decimal rounding modes are the same as the various IEEE binary arithmetic rounding modes. They're part of the same standard and all have their uses.

littledan commented 4 years ago

Great, seems like we all agree that these two proposals should be aligned. (I agree that IEEE 754 is a good guide for rounding modes, but one possibility is that we could decide on a subset.)

justingrant commented 4 years ago

Some bikeshedding feedback on rounding mode names:

The only two I'm suspicious of are floor and ceiling; I wonder if up and down would be enough (or, if floor/ceiling would be enough, without up/down).

IMHO, the names "floor" vs. "down" and "ceiling" vs. "up" may confusingly similar for developers who may not be familiar with IEEE 754 or other platforms' rounding modes. Using all 4 terms may invite confusion.

My suggestion would be to align naming with existing methods used in JS's Math object. By using "trunc" instead of "down", it'd remove the ambiguity noted above. It's also probably better for JS developers if they can leverage existing knowledge of Math instead of having to learn a new (or slightly different) set of terms depending on which JS methods are being used. IMHO this is more important than aligning names to ICU or Java which will be less familiar to JS developers.

So:

We should align the Intl rounding modes with the Decimal rounding modes.

I agree strongly that the same rounding modes and names should be used everywhere: Intl, Decimal, and Temporal.

One exception to alignment might be the default rounding mode used. For example, I could see Decimal's default being roundHalfCiel (because AFAIK that's the expected default for the main use case for Decimal which is financial calculations) while Temporal's default could be trunc (IMHO it would be weird to default to rounding half-days up to a full day without users opting into that behavior).

sffc commented 4 years ago

@gibson042 also suggested looking at the IEEE 754 list of rounding modes:

https://en.wikipedia.org/wiki/Floating-point_arithmetic#Rounding_modes

gibson042 commented 4 years ago

There are already many behaviors in ECMA 262, and I believe each of them should be explicitly specifiable:

I find the ICU names confusing, and would not recommend using them; @justingrant's suggestions are better. If we include ICU "Up" then I would propose "awayFromZero", and likewise "halfAwayFromZero" or "tiesAwayFromZero" for ICU "Half Up".

chyipin commented 4 years ago

Wikipedia covers even more round options https://en.wikipedia.org/wiki/Rounding

Up and Down is ambiguous: i.e. is the value or absolute value higher or lower

To explain ceil or floor, one typically uses round up or round down respectively (thus are synonymous).

I agree that the ICU usage for "Up" and "Down" is uncommon.

I like the recommendation for awayFromZero and possibly towardsZero.

tabatkins commented 4 years ago

For consideration, CSS's round() function chose the values nearest, up, down, and to-zero. (We leave open the possibility of adding away-from-zero later, but as it apparently wasn't useful enough to warrant a dedicated function in JS, we left it out for now.)

We haven't added finer modes for disambiguating nearest at half-integers (currently we always choose the up value), but the grammar is compatible with adding such switches later.

We purposely avoided floor/ceiling because ceiling is hard to spell, and we already had precedent for a similar feature in line layout using the up/down keywords.

(Having CSS's choices flow back into JS, when JS's choices originally informed much of them in this case, would be pretty funny to me.)

gibson042 commented 4 years ago

Kudos on that naming, it seems sensible to equate "up" with "towards +∞" and "down" with "towards -∞", and having "nearest" resolve ties "up" matches lay intuition at least for positive numbers (even if it is mathematically biased). I would not object to adopting all four, preferably also adding a "round ties to even" mode with whatever name seems most likely for CSS.

justingrant commented 4 years ago

I also like CSS naming, but for a JS API I think that matching existing naming elsewhere in JS is probably better than having two different names for the same operations between Intl/Temporal vs. Math.

sffc commented 4 years ago

Okay, so for Intl.NumberFormat, we are currently using "half away from zero" as the default and only rounding mode, which is not available in either 262 or in CSS. So, we need to include that option. How about this:

I don't know if we need "even" or "floating", but the behavior would be:

Number maxFrac Rounding Mode IEEE 32-bit Float Result Comments
1.245 2 even N/A 1.24
1.245 2 floating 1.2450000047683716 1.25 1.25 is odd, but closer to IEEE
1.25 1 even N/A 1.2
1.25 1 floating 1.25 (exact) 1.2 exact midpoint in IEEE; round even

See also https://github.com/tc39/ecma402/issues/128 for more discussion on "floating".

justingrant commented 4 years ago

In writing Temporal sample code recently, the lack of a "round all away from zero" mode has been problematic, because it means you'll need to conditionally change the rounding mode between ceil and floor depending on the sign of value. It'd be good to add this rounding mode too.

sffc commented 4 years ago

From discussion with @gibson042 and @justingrant:

And the tiebreaking modes:

This should cover the complete set.

Additional conclusions:

  1. We expect the plan of action to adopt camel case.
  2. We propose halfExpand as the default.
  3. We could consider using "tie" instead of "half", but we currently plan to move forward with "half".
sffc commented 4 years ago

I opened a Twitter poll for the naming of the "away from 0" option:

https://twitter.com/_sffc/status/1319715712513019905

So far "expand" is the most popular of the options presented, but "Other (please reply)" is currently winning the poll, with the most likes going to "RoundAwayFromZero". This would make the list:

roundingMode:

We could also choose to go all-in with the verbose names and do

roundingMode:

I still prefer introducing a new short word, because I believe people will learn what it means, and we set a precedent for others to follow. However, I will bring this to the TC39 research group for further advice.

CC @mpcsh

sffc commented 3 years ago

2021-02-11: Go with https://github.com/tc39/proposal-intl-numberformat-v3/issues/7#issuecomment-715514928.

sffc commented 3 years ago

ICU ticket: https://unicode-org.atlassian.net/browse/ICU-21493

ICU PR: https://github.com/unicode-org/icu/pull/1575

aleen42 commented 3 years ago

This topic should also be concerned about Number.prototype.toFixed:

(1.02).toFixed(1); // => "1.0"
(1.05).toFixed(1); // => "1.1" round by default

// If we can specify types:
(1.05).toFixed(1, 'floor'); // => "1.0"
(1.02).toFixed(1, 'ceiling'); // => "1.1"
Yaffle commented 3 years ago

@aleen42 , The Number#toFixed works a little differently now, although, it can be changed as well:

(1.05).toFixed(17)
// right now it returns "1.05000000000000004" because 1.05 == 1.0500000000000000444089209850062616169452667236328125 for "Number value"
littledan commented 3 years ago

I really like the "expand" name, and I think that what was discussed and decided above is a reasonable conclusion; I can live with it for application in other proposals like Temporal and Decimal as well.

My only concern here is that the six "half" modes are a bit overkill. It's nice to have the whole logical space thought out, but I'm wondering what will actually be useful. For example, do we have use cases in mind for halfOdd?

Some costs to consider from including so many modes:

These costs are not necessarily too high, but it makes me prefer that we have clear motivation in mind for the modes that we add, rather than just adding all logically possible modes frrom the start.

sffc commented 3 years ago

ICU (and, by extension, the Java SDK), ECMA-262, and CSS all support different sets. The sets we could take are:

  1. All 10 modes.
  2. The union of ECMA-262 and CSS, plus the current ECMA-402 behavior, which gives 6 modes (remove "halfFloor", "halfEven", "halfOdd", and "expand").
  3. The union of ECMA-262, CSS, and ICU, which gives 8 modes (remove "halfFloor" and "halfOdd").

Having implemented rounding modes in ICU, the marginal cost of filling in the set of 10 is extremely minimal relative to either of the two more restrictive sets. Given that the implementation cost is minimal, I find it clearer if we just have the full set, rather than omitting some arbitrary subset based on precedent.

Note: The only rounding mode not being proposed is "floating", available in 262 and CSS but not ICU. Because ICU must convert doubles to strings in order to resolve things like compact and scientific notation, implementing "floating" in ICU would have an additional cost.

caiolima commented 3 years ago

I share Dan's opinion regarding the number of tiebreak modes we have. Particularly, it's strange to me both halfEven and halfOdd. There's real application for halfEven, which justifies the inclusion of such mode if we decide to do so, but I'm curious to know about any usage of halfOdd.

sffc commented 3 years ago

2021-04-06: after discussion with @littledan, @caiolima, @ryzokuken, and @gibson042, we are now recommending the following 9 modes (all except halfOdd):

ceil floor expand trunc halfCeil halfFloor halfExpand halfTrunc halfEven

Note on the decision to recommend camel case: https://github.com/tc39/ecma402/blob/master/docs/style-guide.md#casing-conventions

PaulRBerg commented 1 year ago

I'm late to the party, but I wanted to suggest two alternative names for the "away from 0" rounding mode:

I also like "expand", but it is a tad misleading. Rounding a negative number to a smaller value (e.g. -1.1 → -2) doesn't sound like expanding to me unless you're defining the expansion as being made toward the absolute value

Update: I just saw that in the Mozilla docs, they say that negative values round "more negative". This is an okay-ish framing, but I still like "spread" and "straddle" better.