Skip to content

Commit 20c9f54

Browse files
committed
Document precision considerations of Duration-float methods
A `Duration` is essentially a 94-bit value (64-bit sec and ~30-bit ns), so there's some inherent loss when converting to floating-point for `mul_f64` and `div_f64`. We could go to greater lengths to compute these with more accuracy, like #150933 or #154107, but it's not clear that it's worth the effort. The least we can do is document that some rounding is to be expected, which this commit does with simple examples that only multiply or divide by `1.0`. This also changes the `f32` methods to just forward to `f64`, so we keep more of that duration precision, as the range is otherwise much more limited there.
1 parent 4c42051 commit 20c9f54

File tree

1 file changed

+82
-6
lines changed

1 file changed

+82
-6
lines changed

‎library/core/src/time.rs‎

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,13 +1006,45 @@ impl Duration {
10061006
/// This method will panic if result is negative, overflows `Duration` or not finite.
10071007
///
10081008
/// # Examples
1009+
///
10091010
/// ```
10101011
/// use std::time::Duration;
10111012
///
10121013
/// let dur = Duration::new(2, 700_000_000);
10131014
/// assert_eq!(dur.mul_f64(3.14), Duration::new(8, 478_000_000));
10141015
/// assert_eq!(dur.mul_f64(3.14e5), Duration::new(847_800, 0));
10151016
/// ```
1017+
///
1018+
/// Note that `f64` does not have enough bits ([`f64::MANTISSA_DIGITS`]) to represent the full
1019+
/// range of possible `Duration` with nanosecond precision, so rounding may occur even for
1020+
/// trivial operations like multiplying by 1.
1021+
///
1022+
/// ```
1023+
/// # #![feature(float_exact_integer_constants)]
1024+
/// use std::time::Duration;
1025+
///
1026+
/// // This is about 14.9 weeks, remaining precise to the nanosecond:
1027+
/// let weeks = Duration::from_nanos(f64::MAX_EXACT_INTEGER as u64);
1028+
/// assert_eq!(weeks, weeks.mul_f64(1.0));
1029+
///
1030+
/// // A larger value incurs rounding in the floating-point operation:
1031+
/// let weeks = Duration::from_nanos(u64::MAX);
1032+
/// assert_ne!(weeks, weeks.mul_f64(1.0));
1033+
///
1034+
/// // This is over 285 million years, remaining precise to the second:
1035+
/// let years = Duration::from_secs(f64::MAX_EXACT_INTEGER as u64);
1036+
/// assert_eq!(years, years.mul_f64(1.0));
1037+
///
1038+
/// // And again larger values incur rounding:
1039+
/// let years = Duration::from_secs(u64::MAX / 2);
1040+
/// assert_ne!(years, years.mul_f64(1.0));
1041+
/// ```
1042+
///
1043+
/// ```should_panic
1044+
/// # use std::time::Duration;
1045+
/// // In the extreme, rounding can even overflow `Duration`, which panics.
1046+
/// let _ = Duration::from_secs(u64::MAX).mul_f64(1.0);
1047+
/// ```
10161048
#[stable(feature = "duration_float", since = "1.38.0")]
10171049
#[must_use = "this returns the result of the operation, \
10181050
without modifying the original"]
@@ -1023,6 +1055,10 @@ impl Duration {
10231055

10241056
/// Multiplies `Duration` by `f32`.
10251057
///
1058+
/// Since the significand of `f32` is quite limited compared to the range of `Duration`
1059+
/// -- only about 16.8ms of exact nanosecond precision -- this method currently forwards
1060+
/// to [`mul_f64`][Self::mul_f64] for greater accuracy.
1061+
///
10261062
/// # Panics
10271063
/// This method will panic if result is negative, overflows `Duration` or not finite.
10281064
///
@@ -1031,15 +1067,18 @@ impl Duration {
10311067
/// use std::time::Duration;
10321068
///
10331069
/// let dur = Duration::new(2, 700_000_000);
1034-
/// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_641));
1070+
/// // Note that this `3.14_f32` argument already has more floating-point
1071+
/// // representation error than a direct `3.14_f64` would, so the result
1072+
/// // is slightly different from the ideal 8.478s.
1073+
/// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_283));
10351074
/// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847_800, 0));
10361075
/// ```
10371076
#[stable(feature = "duration_float", since = "1.38.0")]
10381077
#[must_use = "this returns the result of the operation, \
10391078
without modifying the original"]
10401079
#[inline]
10411080
pub fn mul_f32(self, rhs: f32) -> Duration {
1042-
Duration::from_secs_f32(rhs * self.as_secs_f32())
1081+
self.mul_f64(rhs.into())
10431082
}
10441083

10451084
/// Divides `Duration` by `f64`.
@@ -1048,13 +1087,45 @@ impl Duration {
10481087
/// This method will panic if result is negative, overflows `Duration` or not finite.
10491088
///
10501089
/// # Examples
1090+
///
10511091
/// ```
10521092
/// use std::time::Duration;
10531093
///
10541094
/// let dur = Duration::new(2, 700_000_000);
10551095
/// assert_eq!(dur.div_f64(3.14), Duration::new(0, 859_872_611));
10561096
/// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_599));
10571097
/// ```
1098+
///
1099+
/// Note that `f64` does not have enough bits ([`f64::MANTISSA_DIGITS`]) to represent the full
1100+
/// range of possible `Duration` with nanosecond precision, so rounding may occur even for
1101+
/// trivial operations like dividing by 1.
1102+
///
1103+
/// ```
1104+
/// # #![feature(float_exact_integer_constants)]
1105+
/// use std::time::Duration;
1106+
///
1107+
/// // This is about 14.9 weeks, remaining precise to the nanosecond:
1108+
/// let weeks = Duration::from_nanos(f64::MAX_EXACT_INTEGER as u64);
1109+
/// assert_eq!(weeks, weeks.div_f64(1.0));
1110+
///
1111+
/// // A larger value incurs rounding in the floating-point operation:
1112+
/// let weeks = Duration::from_nanos(u64::MAX);
1113+
/// assert_ne!(weeks, weeks.div_f64(1.0));
1114+
///
1115+
/// // This is over 285 million years, remaining precise to the second:
1116+
/// let years = Duration::from_secs(f64::MAX_EXACT_INTEGER as u64);
1117+
/// assert_eq!(years, years.div_f64(1.0));
1118+
///
1119+
/// // And again larger values incur rounding:
1120+
/// let years = Duration::from_secs(u64::MAX / 2);
1121+
/// assert_ne!(years, years.div_f64(1.0));
1122+
/// ```
1123+
///
1124+
/// ```should_panic
1125+
/// # use std::time::Duration;
1126+
/// // In the extreme, rounding can even overflow `Duration`, which panics.
1127+
/// let _ = Duration::from_secs(u64::MAX).div_f64(1.0);
1128+
/// ```
10581129
#[stable(feature = "duration_float", since = "1.38.0")]
10591130
#[must_use = "this returns the result of the operation, \
10601131
without modifying the original"]
@@ -1065,6 +1136,10 @@ impl Duration {
10651136

10661137
/// Divides `Duration` by `f32`.
10671138
///
1139+
/// Since the significand of `f32` is quite limited compared to the range of `Duration`
1140+
/// -- only about 16.8ms of exact nanosecond precision -- this method currently forwards
1141+
/// to [`div_f64`][Self::div_f64] for greater accuracy.
1142+
///
10681143
/// # Panics
10691144
/// This method will panic if result is negative, overflows `Duration` or not finite.
10701145
///
@@ -1073,17 +1148,18 @@ impl Duration {
10731148
/// use std::time::Duration;
10741149
///
10751150
/// let dur = Duration::new(2, 700_000_000);
1076-
/// // note that due to rounding errors result is slightly
1077-
/// // different from 0.859_872_611
1078-
/// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_580));
1151+
/// // Note that this `3.14_f32` argument already has more floating-point
1152+
/// // representation error than a direct `3.14_f64` would, so the result
1153+
/// // is slightly different from the ideally rounded 0.859_872_611.
1154+
/// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_583));
10791155
/// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_599));
10801156
/// ```
10811157
#[stable(feature = "duration_float", since = "1.38.0")]
10821158
#[must_use = "this returns the result of the operation, \
10831159
without modifying the original"]
10841160
#[inline]
10851161
pub fn div_f32(self, rhs: f32) -> Duration {
1086-
Duration::from_secs_f32(self.as_secs_f32() / rhs)
1162+
self.div_f64(rhs.into())
10871163
}
10881164

10891165
/// Divides `Duration` by `Duration` and returns `f64`.

0 commit comments

Comments
 (0)