-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Duration::try_from_secs_f32/64 accuracy by directly processing exponent and mantissa #90247
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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] | ||
|
@@ -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] | ||
|
@@ -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 | ||
/// ``` | ||
|
@@ -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, \ | ||
|
@@ -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 | ||
/// ``` | ||
|
@@ -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 | ||
/// ``` | ||
|
@@ -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)); | ||
/// ``` | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the choice of the Just to clarify: I'm not suggesting to change anything here, and just trying to confirm my understanding. I think I understand how the current
effectively making the current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Offsets are specifically chosen so the final shifts will be equal to 64 and 96 bits respectively, allowing compiler to perform additional optimizations. |
||
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, | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this change reflects the fact that negativity is now checked before anything else, meaning that
-inf
and-NaN
are now aNegative
error instead of aNonFinite
error.(the wording does imply that
+inf
should instead be anOverflow
error instead of aNonFinite
one, only leavingNaN
to be aNonFinite
error)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error kinds are not exposed publicly and only used for generating error descriptions, so I think the order of checks is fairly unimportant. The main reason why I have changed the wording was to fix the ambiguity: the previous variant can be read as both "is not (finite, negative or overflows)" and "is (not finite), negative or overflows".