Skip to content
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

Add rounding mode for #round methods #10228

Closed
straight-shoota opened this issue Jan 9, 2021 · 6 comments · Fixed by #10413
Closed

Add rounding mode for #round methods #10228

straight-shoota opened this issue Jan 9, 2021 · 6 comments · Fixed by #10413

Comments

@straight-shoota
Copy link
Member

In the process of adding rounding methods to BigDecimal, it became evident that existing #round methods (which it should be compatible with) lack a rounding mode option. See discussion in #7126 (comment) ff.

The prototype definition is Number#round(digits = 0, base = 10) : self. The implementations on Int, Float32 and Float64 are performance-oriented overrides with default arguments. There is also Complex#round(digits = 0) : self.

#7126 proposes the introduction of rounding modes to determine rounding behaviour for half steps.
This should be supported by Number#round as well.

For reference, the respective methods in Ruby are Float#round and Integer#round with the signature round([ndigits] [, half: mode]) → integer or float and support rounding mode option as well.

The discussion in #7126 also touches the general usefulness of base argument and supporting base adds more complexity for implementing rounding modes. I agree that there seems little value to this options which isn't available in Ruby and other languages.
But I think potential removal of base argument should be a separate discussion. The current implementation of Number#round(digits = 0, base = 10) can just keep existing next to a new Number#round(digits = 0, mode = :up), so there would be no mixing of base and mode options.

Related: #5714

@straight-shoota
Copy link
Member Author

straight-shoota commented Jan 29, 2021

So I've have a more refined proposal. Most important step is to figure out how to name things, in this case: rounding modes.

Julia's Base.Rounding.RoundingMode looks like a good basis (for the most part). I also took Java's java.math.RoundingMode and MATLAB into consideration. Those were the most comprehensive overviews I could find. Most other libraries have less modes and are similar to either of the ones presented here.
Ruby's half: argument on Float#round accepts only rounding types which disambiguate midway behaviour and the names are equivalent to Java.

Crystal (proposal) Description Matlab Java Julia IEEE-754
FROM_ZERO Rounds away from zero n/a UP FromZero (only big) n/a
TO_ZERO Rounds towards zero (truncate) Zero DOWN ToZero roundTowardZero
CEIL Rounds towards positive infinity Ceiling CEILING Up roundTowardPositive
FLOOR Rounds towards negative infinity Floor FLOOR Down roundTowardNegative
NEAREST_TIES_AWAY Rounds towards the nearest neighbor, unless both neighbors are equidistant in which case rounds away from zero Round HALF_UP NearestTiesAway roundTiesToAway
NEAREST_TIES_ZERO Rounds towards the nearest neighbor, unless both neighbors are equidistant, in which case rounds towards zero n/a HALF_DOWN n/a n/a
NEAREST_TIES_CEIL Rounds towards the nearest neighbor, unless both neighbors are equidistant in which case rounds to positive infinity Nearest n/a NearestTiesUp n/a
NEAREST_TIES_FLOOR Rounds towards the nearest neighbor, unless both neighbors are equidistant in which case rounds to negatvie infinity n/a n/a n/a n/a
NEAREST_TIES_EVEN Rounds towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards the even neighbor (Banker's rounding) Convergent HALF_EVEN Nearest roundTiesToEven

UP and DOWN are really confusing because they have different meanings in different libraries (Java vs. Julia). So it's definitely not a good choice and I opted for the alternatives.

I guess before deciding on names, there's also the question about which ones should be included. I doubt we should support all of them. Even if it's not hard to implement, some of the nearest* modes are rarely supported elsewhere, so I'd presume we can leave some of them out. Later additions are possible, of course.

EDIT: Added rounding modes defined in IEEE-754 to the overview.

@Sija
Copy link
Contributor

Sija commented Jan 29, 2021

@straight-shoota I'd suggest to use Ruby names.

@straight-shoota
Copy link
Member Author

Ruby has only three modes: half: :up, half: :down and half: :even. And up/down has the mentioned problem: Depending on context it relates to either zero (up: from, down: to) or infinity (up: positive, down: negative). half up can be interpreted as what I've called NEAREST_TIES_AWAY or NEAREST_TIES_CEILING. I'd rather avoid ambiguous names.

I also like "nearest" over "half" because it clearly expresses that it generally chooses the nearest value and the specifics only regard tie braking at midpoint.

@Sija
Copy link
Contributor

Sija commented Jan 29, 2021

I was referring to BigDecimal rounding modes since they're the most complete ones:

ROUND_CEILING
Round towards +Infinity.

ROUND_DOWN
Indicates that values should be rounded towards zero.

ROUND_FLOOR
Round towards -Infinity.

ROUND_HALF_DOWN
Indicates that digits >= 6 should be rounded up, others rounded down.

ROUND_HALF_EVEN
Round towards the even neighbor.

ROUND_HALF_UP
Indicates that digits >= 5 should be rounded up, others rounded down.

ROUND_MODE
Determines what happens when a result must be rounded in order to fit in the appropriate number of significant digits.

ROUND_UP
Indicates that values should be rounded away from zero.

@straight-shoota
Copy link
Member Author

Ah, you mean https://ruby-doc.org/stdlib-3.0.0/libdoc/bigdecimal/rdoc/BigDecimal.html#method-c-mode. That link would've helped ;)
So that's basically the same names as in Java.

Still, I'd strongly suggest to avoid up/down for clarity.

@straight-shoota
Copy link
Member Author

straight-shoota commented Feb 3, 2021

As another reference I added the rounding modes defined by IEEE-754 to the overview.

For now, I would only implement the main rounding modes present in all four references. It seems to be a relatively clear distinction between common modes (= supported by all) and uncommon ones (= supported by only 1 or 2 libraries). The common ones are also supported as LLVM intrinsics.

Regarding naming, I also think that TO_POSITIVE and TO_NEGATIVE are more expressive than CEIL and FLOOR which can be harder to understand if you're not familiar with these terms (although they're quite common in this context). "positive" and "negative" are simple to understand.
The "NEAREST" prefix is unnecessary, so we can chop that.

Now this is my updated proposal:

  • TO_ZERO
  • TO_POSITIVE
  • TO_NEGATIVE
  • TIES_EVEN
  • TIES_AWAY

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants