-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
f64::div_euclid and f64::rem_euclid yield inconsistent results #107904
Comments
This comment was marked as resolved.
This comment was marked as resolved.
@edward-shen Thanks for your reply! I think you missed the fn main() {
let numer = -0.7500000000000006f64;
let denom = 0.0833333333333334f64;
let div_euclid = -10.0;
let rem_euclid = 0.0;
assert!((numer.div_euclid(denom) - div_euclid).abs() < f64::EPSILON);
assert!((numer.rem_euclid(denom) - rem_euclid).abs() < f64::EPSILON);
assert!((div_euclid * denom + rem_euclid - numer).abs() < f64::EPSILON);
} Can you reproduce this example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=585eb09aa751c09b8e79e8cec1ae15a7 |
The problem seems to be that although |
@pzorin I think it is very important that the promise I haven't looked into the code yet but it should be possible to achieve a consistent behavior by just calculating one euclidean function based on the other one. E.g. pub fn rem_euclid(self, rhs: f64) -> f64 {
self - self.div_euclid(rhs) * rhs
} |
I've been looking into this for a bit now. Some observations: For the division The defining equation for This can be exact, except for two edge cases:
Notably these two edge cases are disjoint. The remainder may need rounding only if |x| < |y| to begin with, so |n| <= 1. If we do want to specify the result of div_euclid further, it might be reasonable to always round towards negative infinity. That would allow you to compute both an upper bound and a lower bound with
This would satisfy Additionally, it might be reasonable to never return -0.0 for the remainder, even though it compares >= 0.0, so the sign bit would always be positive. I've written an implementation that does all of the above, but it's not currently as fast as it should be. The difficulty is that since |
I find a new pair of numbers yield inconsistent result: fn main() {
let s = 1.6470588235294124_f64;
for a in [46.11764705882353_f64, 46.11764705882354_f64,46.11764705882355_f64] {
println!("{:?} ÷ {:?} = {:?}⋯⋯{:?}", a, s, a.div_euclid(s), a.rem_euclid(s));
}
} The output is:
So 46.11764705882354.div_euclid(1.6470588235294124) should return 27. |
This issue arises due to problems with the default rounding mode, which has been thoroughly analyzed in this paper: Vincent Lefèvre. The Euclidean Division Implemented with a Floating-Point Multiplication and a Floor. 2005. ⟨inria-00000159⟩ |
The current documentation is clear about what should happen (and I think it is reasonable): exact values should be rounded according to usual floating point rounding rules.
Yes, in this case The equation
That could be a useful operation, but that wouldn't be "Euclidean" division.
|
To implement this using integral division for |
I tried this code:
I expected to see this happen:
div_euclid * denom + rem_euclid
should yieldnumer
. So eitherdiv_euclid
should yield-9.0
orrem_euclid
should yield0.0833333333333334f64
. Together they should satisfydiv_euclid * denom + rem_euclid == numer
.Instead, this happened:
div_euclid * denom + rem_euclid
yieldsnumer - denom
. While bothdiv_euclid
andrem_euclid
yield reasonable results in isolation, they are inconsistent which each other. The relationdiv_euclid * denom + rem_euclid == numer
is not satisfied.Meta
rustc --version --verbose
:The text was updated successfully, but these errors were encountered: