Rollup merge of #90247 - newpavlov:duration_float_fract, r=nagisa
Improve Duration::try_from_secs_f32/64 accuracy by directly processing exponent and mantissa Closes: #90225 The methods now implement direct processing of exponent and mantissa, which should result in the best possible conversion accuracy (modulo truncation, i.e. float value of 19.995 ns will be represented as 19 ns).
This commit is contained in:
commit
faf2b7f455
@ -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<Duration, FromSecsError> {
|
||||
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<Duration, FromSecsError> {
|
||||
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<Duration, FromFloatSecsError> {
|
||||
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<Duration, FromFloatSecsError> {
|
||||
try_from_secs!(
|
||||
secs = secs,
|
||||
mantissa_bits = 52,
|
||||
exponent_bits = 11,
|
||||
offset = 44,
|
||||
bits_ty = u64,
|
||||
double_ty = u128,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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`].
|
||||
|
Loading…
Reference in New Issue
Block a user