Skip to content

Commit

Permalink
Rollup merge of #90247 - newpavlov:duration_float_fract, r=nagisa
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
matthiaskrgr authored Jan 26, 2022
2 parents a7f3757 + e0bcf77 commit faf2b7f
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 103 deletions.
301 changes: 200 additions & 101 deletions library/core/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,14 +711,28 @@ impl Duration {
/// 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]
Expand All @@ -731,55 +745,32 @@ impl 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]
Expand All @@ -792,47 +783,10 @@ 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 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
/// ```
Expand All @@ -854,17 +808,15 @@ impl 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, \
Expand All @@ -878,7 +830,7 @@ impl 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
/// ```
Expand All @@ -901,7 +853,7 @@ impl 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
/// ```
Expand All @@ -910,7 +862,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_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));
/// ```
Expand Down Expand Up @@ -1267,33 +1219,180 @@ impl fmt::Debug for Duration {
/// ```
#[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,
)
}
}
Loading

0 comments on commit faf2b7f

Please sign in to comment.