-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Euclidean modulo #2169
Merged
+112
−0
Merged
Euclidean modulo #2169
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e84b3b9
Add an initial proposal for Euclidean modulo
varkor b287ad9
Improve the readability of the proposal
varkor 1b3bf49
Consider floating-point implementations
varkor ffb409d
Fix link
varkor 7e3bc84
Fixed an operator precedence issue
varkor cb8bf0a
Change the name of the Euclidean division/modulo operators
varkor 1650689
Add floating-point Euclidean division and modulo
varkor 0559673
RFC 2169: Euclidean Modulo
alexcrichton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
- Feature Name: euclidean_modulo | ||
- Start Date: 2017-10-09 | ||
- RFC PR: | ||
- Rust Issue: | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
This RFC proposes the addition of a modulo method with more useful and mathematically regular properties over the built-in remainder `%` operator when the dividend or divisor is negative, along with the associated division method. | ||
|
||
For previous discussion, see: https://internals.rust-lang.org/t/mathematical-modulo-operator/5952. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The behaviour of division and modulo, as implemented by Rust's (truncated) division `/` and remainder (or truncated modulo) `%` operators, with respect to negative operands is unintuitive and has fewer useful mathematical properties than that of other varieties of division and modulo, such as flooring and Euclidean[[1]](https://dl.acm.org/citation.cfm?doid=128861.128862). While there are good reasons for this design decision[[2]](https://mail.mozilla.org/pipermail/rust-dev/2013-April/003786.html), having convenient access to a modulo operation, in addition to the remainder is very useful, and has often been requested[[3]](https://mail.mozilla.org/pipermail/rust-dev/2013-April/003680.html)[[4]](https://github.com/rust-lang/rust/issues/13909)[[5]](https://stackoverflow.com/questions/31210357/is-there-a-modulus-not-remainder-function-operation)[[6]](https://users.rust-lang.org/t/proper-modulo-support/903)[[7]](https://www.reddit.com/r/rust/comments/3yoo1q/remainder/). | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
```rust | ||
// Comparison of the behaviour of Rust's truncating division | ||
// and remainder, vs Euclidean division & modulo. | ||
(-8 / 3, -8 % 3) // (-2, -2) | ||
(-8.div_e(3), -8.mod_e(3)) // (-3, 1) | ||
``` | ||
Euclidean division & modulo for integers will be achieved using the `div_e` and `mod_e` methods. The `%` operator has identical behaviour to `mod_e` for unsigned integers. However, when using signed integers, you should be careful to consider the behaviour you want: often Euclidean modulo will be more appropriate. | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
It is important to have both division and modulo methods, as the two operations are intrinsically linked[[8]](https://en.wikipedia.org/wiki/Modulo_operation), though it is often the modulo operator that is specifically requested. | ||
|
||
A complete implementation of Euclidean modulo would involve adding 8 methods to the integer primitives in `libcore/num/mod.rs`: | ||
```rust | ||
// Implemented for all numeric primitives. | ||
fn div_e(self, rhs: Self) -> Self; | ||
|
||
fn mod_e(self, rhs: Self) -> Self; | ||
|
||
// Implemented for all integer primitives (signed and unsigned). | ||
fn checked_div_e(self, other: Self) -> Option<Self>; | ||
fn overflowing_div_e(self, rhs: Self) -> (Self, bool); | ||
fn wrapping_div_e(self, rhs: Self) -> Self; | ||
|
||
fn checked_mod_e(self, other: Self) -> Option<Self>; | ||
fn overflowing_mod_e(self, rhs: Self) -> (Self, bool); | ||
fn wrapping_mod_e(self, rhs: Self) -> Self; | ||
``` | ||
|
||
Sample implementations for `div_e` and `mod_e` on signed integers: | ||
```rust | ||
fn div_e(self, rhs: Self) -> Self { | ||
let q = self / rhs; | ||
if self % rhs < 0 { | ||
return if rhs > 0 { q - 1 } else { q + 1 } | ||
} | ||
q | ||
} | ||
|
||
fn mod_e(self, rhs: Self) -> Self { | ||
let r = self % rhs; | ||
if r < 0 { | ||
return if rhs > 0 { r + rhs } else { r - rhs } | ||
} | ||
r | ||
} | ||
``` | ||
|
||
The unsigned implementations of these methods are trivial. | ||
The `checked_*`, `overflowing_*` and `wrapping_*` methods would operate analogously to their non-Euclidean `*_div` and `*_rem` counterparts that already exist. The edge cases are identical. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
Standard drawbacks of adding methods to primitives apply. However, with the proposed method names, there are unlikely to be conflicts downstream[[9]](https://github.com/search?q=div_e+language%3ARust&type=Code&utf8=%E2%9C%93)[[10]](https://github.com/search?q=mod_e+language%3ARust&type=Code&utf8=%E2%9C%93). | ||
|
||
# Rationale and alternatives | ||
[alternatives]: #alternatives | ||
|
||
Flooring modulo is another variant that also has more useful behaviour with negative dividends than the remainder (truncating modulo). The difference in behaviour between flooring and Euclidean division & modulo come up rarely in practice, but there are arguments in favour of the mathematical properties of Euclidean division and modulo[[1]](https://dl.acm.org/citation.cfm?doid=128861.128862). Alternatively, both methods (flooring _and_ Euclidean) could be made available, though the difference between the two is likely specialised-enough that this would be overkill. | ||
|
||
The functionality could be provided as an operator. However, it is likely that the functionality of remainder and modulo are small enough that it is not worth providing a dedicated operator for the method. | ||
|
||
This functionality could instead reside in a separate crate, such as `num` (floored division & modulo is already available in this crate). However, there are strong points for inclusion into core itself: | ||
- Modulo as an operation is more often desirable than remainder for signed operations (so much so that it is the default in a number of languages) -- [the mailing list discussion has more support in favour of flooring/Euclidean division](https://mail.mozilla.org/pipermail/rust-dev/2013-April/003687.html). | ||
- Many people are unaware that the remainder can cause problems with signed integers, and having a method displaying the other behaviour would draw attention to this subtlety. | ||
- The previous support for this functionality in core shows that many are keen to have this available. | ||
- The Euclidean or flooring modulo is used (or reimplemented) commonly enough that it is worth having it generally accessible, rather than in a separate crate that must be depended on by each project. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Is it worth implementing `div_e` and `mod_e` for floating-point numbers? While it makes sense for completeness, it may be too rare a use case to be worth extending the core library to include. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.
has higher precedence than unary minus. This will be evaluated as-( 8.mod_e(3) )
i.e.-2
. You want(-8).mod_e(3)
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks!