Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 62 additions & 6 deletions library/core/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`.
Expand All @@ -1031,15 +1078,18 @@ 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")]
#[must_use = "this returns the result of the operation, \
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`.
Expand All @@ -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`.
Expand All @@ -1075,15 +1128,18 @@ 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")]
#[must_use = "this returns the result of the operation, \
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`.
Expand Down
60 changes: 57 additions & 3 deletions library/coretests/tests/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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));
}
Loading