From e0bcf771d6e670988a3d4fdc785ecd5857916f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 26 Jan 2022 18:14:25 +0300 Subject: [PATCH] Improve Duration::try_from_secs_f32/64 accuracy by directly processing exponent and mantissa --- library/core/src/time.rs | 301 ++++++++++++++++++++++++++------------- library/std/src/error.rs | 2 +- library/std/src/time.rs | 2 +- 3 files changed, 202 insertions(+), 103 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 746d1cacfd0..243c044b5d9 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -711,14 +711,28 @@ pub const fn as_secs_f32(&self) -> f32 { /// as `f64`. /// /// # Panics - /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. + /// This constructor will panic if `secs` is negative, overflows `Duration` or not finite. /// /// # Examples /// ``` /// use std::time::Duration; /// - /// let dur = Duration::from_secs_f64(2.7); - /// assert_eq!(dur, Duration::new(2, 700_000_000)); + /// let res = Duration::from_secs_f64(0.0); + /// assert_eq!(res, Duration::new(0, 0)); + /// let res = Duration::from_secs_f64(1e-20); + /// assert_eq!(res, Duration::new(0, 0)); + /// let res = Duration::from_secs_f64(4.2e-7); + /// assert_eq!(res, Duration::new(0, 420)); + /// let res = Duration::from_secs_f64(2.7); + /// assert_eq!(res, Duration::new(2, 700_000_000)); + /// let res = Duration::from_secs_f64(3e10); + /// assert_eq!(res, Duration::new(30_000_000_000, 0)); + /// // subnormal float + /// let res = Duration::from_secs_f64(f64::from_bits(1)); + /// assert_eq!(res, Duration::new(0, 0)); + /// // conversion uses truncation, not rounding + /// let res = Duration::from_secs_f64(0.999e-9); + /// assert_eq!(res, Duration::new(0, 0)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use] @@ -731,55 +745,32 @@ pub const fn from_secs_f64(secs: f64) -> Duration { } } - /// The checked version of [`from_secs_f64`]. - /// - /// [`from_secs_f64`]: Duration::from_secs_f64 - /// - /// This constructor will return an `Err` if `secs` is not finite, negative or overflows `Duration`. - /// - /// # Examples - /// ``` - /// #![feature(duration_checked_float)] - /// use std::time::Duration; - /// - /// let dur = Duration::try_from_secs_f64(2.7); - /// assert_eq!(dur, Ok(Duration::new(2, 700_000_000))); - /// - /// let negative = Duration::try_from_secs_f64(-5.0); - /// assert!(negative.is_err()); - /// ``` - #[unstable(feature = "duration_checked_float", issue = "83400")] - #[inline] - pub const fn try_from_secs_f64(secs: f64) -> Result { - const MAX_NANOS_F64: f64 = ((u64::MAX as u128 + 1) * (NANOS_PER_SEC as u128)) as f64; - let nanos = secs * (NANOS_PER_SEC as f64); - if !nanos.is_finite() { - Err(FromSecsError { kind: FromSecsErrorKind::NonFinite }) - } else if nanos >= MAX_NANOS_F64 { - Err(FromSecsError { kind: FromSecsErrorKind::Overflow }) - } else if nanos < 0.0 { - Err(FromSecsError { kind: FromSecsErrorKind::Negative }) - } else { - let nanos = nanos as u128; - Ok(Duration { - secs: (nanos / (NANOS_PER_SEC as u128)) as u64, - nanos: (nanos % (NANOS_PER_SEC as u128)) as u32, - }) - } - } - /// Creates a new `Duration` from the specified number of seconds represented /// as `f32`. /// /// # Panics - /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. + /// This constructor will panic if `secs` is negative, overflows `Duration` or not finite. /// /// # Examples /// ``` /// use std::time::Duration; /// - /// let dur = Duration::from_secs_f32(2.7); - /// assert_eq!(dur, Duration::new(2, 700_000_000)); + /// let res = Duration::from_secs_f32(0.0); + /// assert_eq!(res, Duration::new(0, 0)); + /// let res = Duration::from_secs_f32(1e-20); + /// assert_eq!(res, Duration::new(0, 0)); + /// let res = Duration::from_secs_f32(4.2e-7); + /// assert_eq!(res, Duration::new(0, 419)); + /// let res = Duration::from_secs_f32(2.7); + /// assert_eq!(res, Duration::new(2, 700_000_047)); + /// let res = Duration::from_secs_f32(3e10); + /// assert_eq!(res, Duration::new(30_000_001_024, 0)); + /// // subnormal float + /// let res = Duration::from_secs_f32(f32::from_bits(1)); + /// assert_eq!(res, Duration::new(0, 0)); + /// // conversion uses truncation, not rounding + /// let res = Duration::from_secs_f32(0.999e-9); + /// assert_eq!(res, Duration::new(0, 0)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use] @@ -792,47 +783,10 @@ pub const fn from_secs_f32(secs: f32) -> Duration { } } - /// The checked version of [`from_secs_f32`]. - /// - /// [`from_secs_f32`]: Duration::from_secs_f32 - /// - /// This constructor will return an `Err` if `secs` is not finite, negative or overflows `Duration`. - /// - /// # Examples - /// ``` - /// #![feature(duration_checked_float)] - /// use std::time::Duration; - /// - /// let dur = Duration::try_from_secs_f32(2.7); - /// assert_eq!(dur, Ok(Duration::new(2, 700_000_000))); - /// - /// let negative = Duration::try_from_secs_f32(-5.0); - /// assert!(negative.is_err()); - /// ``` - #[unstable(feature = "duration_checked_float", issue = "83400")] - #[inline] - pub const fn try_from_secs_f32(secs: f32) -> Result { - const MAX_NANOS_F32: f32 = ((u64::MAX as u128 + 1) * (NANOS_PER_SEC as u128)) as f32; - let nanos = secs * (NANOS_PER_SEC as f32); - if !nanos.is_finite() { - Err(FromSecsError { kind: FromSecsErrorKind::NonFinite }) - } else if nanos >= MAX_NANOS_F32 { - Err(FromSecsError { kind: FromSecsErrorKind::Overflow }) - } else if nanos < 0.0 { - Err(FromSecsError { kind: FromSecsErrorKind::Negative }) - } else { - let nanos = nanos as u128; - Ok(Duration { - secs: (nanos / (NANOS_PER_SEC as u128)) as u64, - nanos: (nanos % (NANOS_PER_SEC as u128)) as u32, - }) - } - } - /// Multiplies `Duration` by `f64`. /// /// # Panics - /// This method will panic if result is not finite, negative or overflows `Duration`. + /// This method will panic if result is negative, overflows `Duration` or not finite. /// /// # Examples /// ``` @@ -854,17 +808,15 @@ pub const fn mul_f64(self, rhs: f64) -> Duration { /// Multiplies `Duration` by `f32`. /// /// # Panics - /// This method will panic if result is not finite, negative or overflows `Duration`. + /// This method will panic if result is negative, overflows `Duration` or not finite. /// /// # Examples /// ``` /// use std::time::Duration; /// /// let dur = Duration::new(2, 700_000_000); - /// // note that due to rounding errors result is slightly different - /// // from 8.478 and 847800.0 /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_640)); - /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847799, 969_120_256)); + /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847800, 0)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use = "this returns the result of the operation, \ @@ -878,7 +830,7 @@ pub const fn mul_f32(self, rhs: f32) -> Duration { /// Divide `Duration` by `f64`. /// /// # Panics - /// This method will panic if result is not finite, negative or overflows `Duration`. + /// This method will panic if result is negative, overflows `Duration` or not finite. /// /// # Examples /// ``` @@ -901,7 +853,7 @@ pub const fn div_f64(self, rhs: f64) -> Duration { /// Divide `Duration` by `f32`. /// /// # Panics - /// This method will panic if result is not finite, negative or overflows `Duration`. + /// This method will panic if result is negative, overflows `Duration` or not finite. /// /// # Examples /// ``` @@ -910,7 +862,7 @@ pub const fn div_f64(self, rhs: f64) -> 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_576)); + /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_579)); /// // note that truncation is used, not rounding /// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_598)); /// ``` @@ -1267,33 +1219,180 @@ fn fmt_decimal( /// ``` #[derive(Debug, Clone, PartialEq, Eq)] #[unstable(feature = "duration_checked_float", issue = "83400")] -pub struct FromSecsError { - kind: FromSecsErrorKind, +pub struct FromFloatSecsError { + kind: FromFloatSecsErrorKind, } -impl FromSecsError { +impl FromFloatSecsError { const fn description(&self) -> &'static str { match self.kind { - FromSecsErrorKind::NonFinite => "non-finite value when converting float to duration", - FromSecsErrorKind::Overflow => "overflow when converting float to duration", - FromSecsErrorKind::Negative => "negative value when converting float to duration", + FromFloatSecsErrorKind::Negative => { + "can not convert float seconds to Duration: value is negative" + } + FromFloatSecsErrorKind::OverflowOrNan => { + "can not convert float seconds to Duration: value is either too big or NaN" + } } } } #[unstable(feature = "duration_checked_float", issue = "83400")] -impl fmt::Display for FromSecsError { +impl fmt::Display for FromFloatSecsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.description(), f) + self.description().fmt(f) } } #[derive(Debug, Clone, PartialEq, Eq)] -enum FromSecsErrorKind { - // Value is not a finite value (either + or - infinity or NaN). - NonFinite, - // Value is too large to store in a `Duration`. - Overflow, +enum FromFloatSecsErrorKind { // Value is negative. Negative, + // Value is either too big to be represented as `Duration` or `NaN`. + OverflowOrNan, +} + +macro_rules! try_from_secs { + ( + secs = $secs: expr, + mantissa_bits = $mant_bits: literal, + exponent_bits = $exp_bits: literal, + offset = $offset: literal, + bits_ty = $bits_ty:ty, + double_ty = $double_ty:ty, + ) => {{ + const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2; + const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1; + const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1; + + if $secs.is_sign_negative() { + return Err(FromFloatSecsError { kind: FromFloatSecsErrorKind::Negative }); + } + + let bits = $secs.to_bits(); + let mant = (bits & MANT_MASK) | (MANT_MASK + 1); + let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP; + + let (secs, nanos) = if exp < -30 { + // the input represents less than 1ns. + (0u64, 0u32) + } else if exp < 0 { + // the input is less than 1 second + let t = <$double_ty>::from(mant) << ($offset + exp); + let nanos = (u128::from(NANOS_PER_SEC) * u128::from(t)) >> ($mant_bits + $offset); + (0, nanos as u32) + } else if exp < $mant_bits { + let secs = mant >> ($mant_bits - exp); + let t = <$double_ty>::from((mant << exp) & MANT_MASK); + let nanos = (<$double_ty>::from(NANOS_PER_SEC) * t) >> $mant_bits; + (u64::from(secs), nanos as u32) + } else if exp < 64 { + // the input has no fractional part + let secs = u64::from(mant) << (exp - $mant_bits); + (secs, 0) + } else { + return Err(FromFloatSecsError { kind: FromFloatSecsErrorKind::OverflowOrNan }); + }; + + Ok(Duration { secs, nanos }) + }}; +} + +impl Duration { + /// The checked version of [`from_secs_f32`]. + /// + /// [`from_secs_f32`]: Duration::from_secs_f32 + /// + /// This constructor will return an `Err` if `secs` is negative, overflows `Duration` or not finite. + /// + /// # Examples + /// ``` + /// #![feature(duration_checked_float)] + /// + /// use std::time::Duration; + /// + /// let res = Duration::try_from_secs_f32(0.0); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// let res = Duration::try_from_secs_f32(1e-20); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// let res = Duration::try_from_secs_f32(4.2e-7); + /// assert_eq!(res, Ok(Duration::new(0, 419))); + /// let res = Duration::try_from_secs_f32(2.7); + /// assert_eq!(res, Ok(Duration::new(2, 700_000_047))); + /// let res = Duration::try_from_secs_f32(3e10); + /// assert_eq!(res, Ok(Duration::new(30_000_001_024, 0))); + /// // subnormal float: + /// let res = Duration::try_from_secs_f32(f32::from_bits(1)); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// // conversion uses truncation, not rounding + /// let res = Duration::try_from_secs_f32(0.999e-9); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// + /// let res = Duration::try_from_secs_f32(-5.0); + /// assert!(res.is_err()); + /// let res = Duration::try_from_secs_f32(f32::NAN); + /// assert!(res.is_err()); + /// let res = Duration::try_from_secs_f32(2e19); + /// assert!(res.is_err()); + /// ``` + #[unstable(feature = "duration_checked_float", issue = "83400")] + #[inline] + pub const fn try_from_secs_f32(secs: f32) -> Result { + try_from_secs!( + secs = secs, + mantissa_bits = 23, + exponent_bits = 8, + offset = 41, + bits_ty = u32, + double_ty = u64, + ) + } + + /// The checked version of [`from_secs_f64`]. + /// + /// [`from_secs_f64`]: Duration::from_secs_f64 + /// + /// This constructor will return an `Err` if `secs` is negative, overflows `Duration` or not finite. + /// + /// # Examples + /// ``` + /// #![feature(duration_checked_float)] + /// + /// use std::time::Duration; + /// + /// let res = Duration::try_from_secs_f64(0.0); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// let res = Duration::try_from_secs_f64(1e-20); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// let res = Duration::try_from_secs_f64(4.2e-7); + /// assert_eq!(res, Ok(Duration::new(0, 420))); + /// let res = Duration::try_from_secs_f64(2.7); + /// assert_eq!(res, Ok(Duration::new(2, 700_000_000))); + /// let res = Duration::try_from_secs_f64(3e10); + /// assert_eq!(res, Ok(Duration::new(30_000_000_000, 0))); + /// // subnormal float + /// let res = Duration::try_from_secs_f64(f64::from_bits(1)); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// // conversion uses truncation, not rounding + /// let res = Duration::try_from_secs_f32(0.999e-9); + /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// + /// let res = Duration::try_from_secs_f64(-5.0); + /// assert!(res.is_err()); + /// let res = Duration::try_from_secs_f64(f64::NAN); + /// assert!(res.is_err()); + /// let res = Duration::try_from_secs_f64(2e19); + /// assert!(res.is_err()); + /// ``` + #[unstable(feature = "duration_checked_float", issue = "83400")] + #[inline] + pub const fn try_from_secs_f64(secs: f64) -> Result { + try_from_secs!( + secs = secs, + mantissa_bits = 52, + exponent_bits = 11, + offset = 44, + bits_ty = u64, + double_ty = u128, + ) + } } diff --git a/library/std/src/error.rs b/library/std/src/error.rs index 643108b88bf..1a96b9c9282 100644 --- a/library/std/src/error.rs +++ b/library/std/src/error.rs @@ -602,7 +602,7 @@ fn description(&self) -> &str { impl Error for alloc::collections::TryReserveError {} #[unstable(feature = "duration_checked_float", issue = "83400")] -impl Error for time::FromSecsError {} +impl Error for time::FromFloatSecsError {} // Copied from `any.rs`. impl dyn Error + 'static { diff --git a/library/std/src/time.rs b/library/std/src/time.rs index b6867e68df7..b4f9d8ea28d 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -45,7 +45,7 @@ pub use core::time::Duration; #[unstable(feature = "duration_checked_float", issue = "83400")] -pub use core::time::FromSecsError; +pub use core::time::FromFloatSecsError; /// A measurement of a monotonically nondecreasing clock. /// Opaque and useful only with [`Duration`].