Skip to content

Commit 63531f5

Browse files
authored
Rollup merge of rust-lang#50342 - fkjogu:euclidean, r=BurntSushi
Document round-off error in `.mod_euc()`-method, see issue rust-lang#50179 Due to a round-off error the method `.mod_euc()` of both `f32` and `f64` can produce mathematical invalid outputs. If `self` in magnitude is much small than the modulus `rhs` and negative, `self + rhs` in the first branch cannot be represented in the given precision and results into `rhs`. In the mathematical strict sense, this breaks the definition. But given the limitation of floating point arithmetic it can be thought of the closest representable value to the true result, although it is not strictly in the domain `[0.0, rhs)` of the function. It is rather the left side asymptotical limit. It would be desirable that it produces the mathematical more sound approximation of `0.0`, the right side asymptotical limit. But this breaks the property, that `self == self.div_euc(rhs) * rhs + a.mod_euc(rhs)`. The discussion in issue rust-lang#50179 did not find an satisfying conclusion to which property is deemed more important. But at least we can document the behaviour. Which this pull request does.
2 parents e5462b8 + bd853a6 commit 63531f5

File tree

3 files changed

+39
-2
lines changed

3 files changed

+39
-2
lines changed

src/libcore/tests/num/mod.rs

+19
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,25 @@ macro_rules! test_float {
574574
assert_eq!((-9.0 as $fty).max($nan), -9.0);
575575
assert!(($nan as $fty).max($nan).is_nan());
576576
}
577+
#[test]
578+
fn mod_euc() {
579+
let a: $fty = 42.0;
580+
assert!($inf.mod_euc(a).is_nan());
581+
assert_eq!(a.mod_euc($inf), a);
582+
assert!(a.mod_euc($nan).is_nan());
583+
assert!($inf.mod_euc($inf).is_nan());
584+
assert!($inf.mod_euc($nan).is_nan());
585+
assert!($nan.mod_euc($inf).is_nan());
586+
}
587+
#[test]
588+
fn div_euc() {
589+
let a: $fty = 42.0;
590+
assert_eq!(a.div_euc($inf), 0.0);
591+
assert!(a.div_euc($nan).is_nan());
592+
assert!($inf.div_euc($inf).is_nan());
593+
assert!($inf.div_euc($nan).is_nan());
594+
assert!($nan.div_euc($inf).is_nan());
595+
}
577596
} }
578597
}
579598

src/libstd/f32.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,14 @@ impl f32 {
254254

255255
/// Calculates the Euclidean modulo (self mod rhs), which is never negative.
256256
///
257-
/// In particular, the result `n` satisfies `0 <= n < rhs.abs()`.
257+
/// In particular, the return value `r` satisfies `0.0 <= r < rhs.abs()` in
258+
/// most cases. However, due to a floating point round-off error it can
259+
/// result in `r == rhs.abs()`, violating the mathematical definition, if
260+
/// `self` is much smaller than `rhs.abs()` in magnitude and `self < 0.0`.
261+
/// This result is not an element of the function's codomain, but it is the
262+
/// closest floating point number in the real numbers and thus fulfills the
263+
/// property `self == self.div_euc(rhs) * rhs + self.mod_euc(rhs)`
264+
/// approximatively.
258265
///
259266
/// # Examples
260267
///
@@ -266,6 +273,8 @@ impl f32 {
266273
/// assert_eq!((-a).mod_euc(b), 1.0);
267274
/// assert_eq!(a.mod_euc(-b), 3.0);
268275
/// assert_eq!((-a).mod_euc(-b), 1.0);
276+
/// // limitation due to round-off error
277+
/// assert!((-std::f32::EPSILON).mod_euc(3.0) != 0.0);
269278
/// ```
270279
#[inline]
271280
#[unstable(feature = "euclidean_division", issue = "49048")]

src/libstd/f64.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,14 @@ impl f64 {
230230

231231
/// Calculates the Euclidean modulo (self mod rhs), which is never negative.
232232
///
233-
/// In particular, the result `n` satisfies `0 <= n < rhs.abs()`.
233+
/// In particular, the return value `r` satisfies `0.0 <= r < rhs.abs()` in
234+
/// most cases. However, due to a floating point round-off error it can
235+
/// result in `r == rhs.abs()`, violating the mathematical definition, if
236+
/// `self` is much smaller than `rhs.abs()` in magnitude and `self < 0.0`.
237+
/// This result is not an element of the function's codomain, but it is the
238+
/// closest floating point number in the real numbers and thus fulfills the
239+
/// property `self == self.div_euc(rhs) * rhs + self.mod_euc(rhs)`
240+
/// approximatively.
234241
///
235242
/// # Examples
236243
///
@@ -242,6 +249,8 @@ impl f64 {
242249
/// assert_eq!((-a).mod_euc(b), 1.0);
243250
/// assert_eq!(a.mod_euc(-b), 3.0);
244251
/// assert_eq!((-a).mod_euc(-b), 1.0);
252+
/// // limitation due to round-off error
253+
/// assert!((-std::f64::EPSILON).mod_euc(3.0) != 0.0);
245254
/// ```
246255
#[inline]
247256
#[unstable(feature = "euclidean_division", issue = "49048")]

0 commit comments

Comments
 (0)