diff --git a/library/core/src/time.rs b/library/core/src/time.rs index a5b654033ba14..57166b09e5b2c 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1000,6 +1000,50 @@ impl Duration { } } + #[inline] + #[track_caller] + #[cfg(target_has_reliable_f128)] + fn from_nanos_f128_subnormal(nanos: f128) -> Duration { + // NB: the mul/div methods below are converting nanos with `f128::from_bits`, which puts + // them in the subnormal range, with the value packed in the least-significant bits of the + // mantissa -- even `Duration::MAX` fits this way. This avoids double-rounding operations, + // since we're not keeping any excess precision, and we can convert back `to_bits()`. + if nanos < 0.0 { + panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::Negative }); + } else if nanos == 0.0 { + // In particular, -0.0 is an exception that we can't just + // convert `to_bits()`, but it's still a valid zero. + return Duration::ZERO; + } + let nanos = nanos.to_bits(); + if nanos > const { Self::MAX.as_nanos() } { + panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan }); + } + Self::from_nanos_u128(nanos) + } + + #[inline] + #[track_caller] + #[cfg(target_has_reliable_f128)] + fn mul_f128(self, rhs: f128) -> Duration { + if rhs < 0.0 && self != Self::ZERO { + panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::Negative }); + } + let nanos = f128::from_bits(self.as_nanos()) * (rhs as f128); + Self::from_nanos_f128_subnormal(nanos) + } + + #[inline] + #[track_caller] + #[cfg(target_has_reliable_f128)] + fn div_f128(self, rhs: f128) -> Duration { + if rhs < 0.0 && rhs.is_finite() && self != Self::ZERO { + panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::Negative }); + } + let nanos = f128::from_bits(self.as_nanos()) / (rhs as f128); + Self::from_nanos_f128_subnormal(nanos) + } + /// Multiplies `Duration` by `f64`. /// /// # Panics @@ -1018,7 +1062,10 @@ impl Duration { without modifying the original"] #[inline] pub fn mul_f64(self, rhs: f64) -> Duration { - Duration::from_secs_f64(rhs * self.as_secs_f64()) + crate::cfg_select! { + target_has_reliable_f128 => self.mul_f128(rhs.into()), + _ => Duration::from_secs_f64(rhs * self.as_secs_f64()), + } } /// Multiplies `Duration` by `f32`. @@ -1031,7 +1078,7 @@ impl Duration { /// use std::time::Duration; /// /// let dur = Duration::new(2, 700_000_000); - /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_641)); + /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_283)); /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847_800, 0)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] @@ -1039,7 +1086,10 @@ impl Duration { without modifying the original"] #[inline] pub fn mul_f32(self, rhs: f32) -> Duration { - Duration::from_secs_f32(rhs * self.as_secs_f32()) + crate::cfg_select! { + target_has_reliable_f128 => self.mul_f128(rhs.into()), + _ => self.mul_f64(rhs.into()), + } } /// Divides `Duration` by `f64`. @@ -1060,7 +1110,10 @@ impl Duration { without modifying the original"] #[inline] pub fn div_f64(self, rhs: f64) -> Duration { - Duration::from_secs_f64(self.as_secs_f64() / rhs) + crate::cfg_select! { + target_has_reliable_f128 => self.div_f128(rhs.into()), + _ => Duration::from_secs_f64(self.as_secs_f64() / rhs), + } } /// Divides `Duration` by `f32`. @@ -1075,7 +1128,7 @@ impl Duration { /// let dur = Duration::new(2, 700_000_000); /// // note that due to rounding errors result is slightly /// // different from 0.859_872_611 - /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_580)); + /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_583)); /// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_599)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] @@ -1083,7 +1136,10 @@ impl Duration { without modifying the original"] #[inline] pub fn div_f32(self, rhs: f32) -> Duration { - Duration::from_secs_f32(self.as_secs_f32() / rhs) + crate::cfg_select! { + target_has_reliable_f128 => self.div_f128(rhs.into()), + _ => self.div_f64(rhs.into()), + } } /// Divides `Duration` by `Duration` and returns `f64`. diff --git a/library/coretests/tests/time.rs b/library/coretests/tests/time.rs index 5877f662b7ddc..12f0da4d37d28 100644 --- a/library/coretests/tests/time.rs +++ b/library/coretests/tests/time.rs @@ -642,10 +642,20 @@ fn duration_fp_div_negative() { } const TOO_LARGE_FACTOR: f64 = Duration::MAX.as_nanos() as f64; -const TOO_LARGE_DIVISOR: f64 = (Duration::MAX.as_secs_f64() * 2e9).next_up(); -const SMALLEST_DIVISOR: f64 = (TOO_LARGE_DIVISOR.recip() * 2.0).next_up().next_up(); const SMALLEST_FACTOR: f64 = TOO_LARGE_FACTOR.recip() / 2.0; -const SMALLEST_NEGFACTOR: f64 = (0.0f64.next_down() * 0.5e9).next_up(); + +cfg_select! { + target_has_reliable_f128 => { + const TOO_LARGE_DIVISOR: f64 = TOO_LARGE_FACTOR * 2.0; + const SMALLEST_DIVISOR: f64 = TOO_LARGE_DIVISOR.recip() * 2.0; + const SMALLEST_NEGFACTOR: f64 = -0.0f64; + } + _ => { + const TOO_LARGE_DIVISOR: f64 = (Duration::MAX.as_secs_f64() * 2e9).next_up(); + const SMALLEST_DIVISOR: f64 = (TOO_LARGE_DIVISOR.recip() * 2.0).next_up().next_up(); + const SMALLEST_NEGFACTOR: f64 = (0.0f64.next_down() * 0.5e9).next_up(); + } +} #[test] fn duration_fp_boundaries() { @@ -695,3 +705,47 @@ fn duration_fp_mul_overflow() { fn duration_fp_div_overflow() { let _ = Duration::NANOSECOND.div_f64(SMALLEST_DIVISOR.next_down()); } + +#[test] +#[cfg_attr(not(target_has_reliable_f128), ignore)] +fn precise_duration_fp_mul() { + let d1 = Duration::from_nanos_u128(1 << 90); + let d2 = Duration::from_nanos_u128(2 << 90); + let d3 = Duration::from_nanos_u128(3 << 90); + + assert_eq!(d1.mul_f32(1.0), d1); + assert_eq!(d1.mul_f32(2.0), d2); + assert_eq!(d1.mul_f32(3.0), d3); + assert_eq!(d2.mul_f32(1.5), d3); + + let _ = Duration::MAX.mul_f32(1.0); +} + +#[test] +#[cfg_attr(not(target_has_reliable_f128), ignore)] +fn precise_duration_fp_div() { + let d1 = Duration::from_nanos_u128(1 << 90); + let d2 = Duration::from_nanos_u128(2 << 90); + let d3 = Duration::from_nanos_u128(3 << 90); + + assert_eq!(d1.div_f32(1.0), d1); + assert_eq!(d2.div_f32(2.0), d1); + assert_eq!(d3.div_f32(3.0), d1); + assert_eq!(d3.div_f32(1.5), d2); + + let _ = Duration::MAX.div_f32(1.0); +} + +#[test] +#[cfg_attr(not(target_has_reliable_f128), ignore)] +fn duration_fp_mul_rounding() { + // This precise result in ns would start 9223372036854777855999999999.4999999999999998... + // If that is rounded too early to 9223372036854777855999999999.5, + // then the final result would be incorrectly rounded up again. + assert_eq!( + Duration::MAX.mul_f64(0.5_f64.next_up()), + Duration::from_nanos_u128(9223372036854777855999999999) + ); + // This is precisely 9223372036854775807999999999.5 ns, which *should* round up. + assert_eq!(Duration::MAX.mul_f64(0.5_f64), Duration::from_secs(9223372036854775808)); +}