Skip to content

Commit bde3232

Browse files
CryZeTheTedder
andcommitted
Port Duration::from_secs_f32|64 from std
This ports the algorithm for constructing `Duration` from floating point seconds from `std`. The new algorithm in `std` uses bit manipulation to construct the `Duration` with the most precision possible. Additionally this adds `saturating` and `checked` variants of those functions. Original `std` PR: rust-lang/rust#90247 Co-authored-by: Ted Wollman <[email protected]>
1 parent 22ae579 commit bde3232

File tree

1 file changed

+259
-22
lines changed

1 file changed

+259
-22
lines changed

time/src/duration.rs

Lines changed: 259 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,123 @@ impl fmt::Debug for Duration {
5151
}
5252
}
5353

54+
/// This is adapted from the `std` implementation, which uses mostly bit
55+
/// operations to ensure the highest precision:
56+
/// https://github.com/rust-lang/rust/blob/3a37c2f0523c87147b64f1b8099fc9df22e8c53e/library/core/src/time.rs#L1262-L1340
57+
/// Changes from `std` are marked and explained below.
58+
macro_rules! try_from_secs {
59+
(
60+
secs = $secs: expr,
61+
mantissa_bits = $mant_bits: literal,
62+
exponent_bits = $exp_bits: literal,
63+
offset = $offset: literal,
64+
bits_ty = $bits_ty:ty,
65+
bits_ty_signed = $bits_ty_signed:ty,
66+
double_ty = $double_ty:ty,
67+
float_ty = $float_ty:ty,
68+
is_nan = $is_nan:expr,
69+
is_overflow = $is_overflow:expr,
70+
) => {{
71+
'value: {
72+
const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2;
73+
const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1;
74+
const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1;
75+
76+
// Change from std: No error check for negative values necessary.
77+
78+
let bits = $secs.to_bits();
79+
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
80+
let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP;
81+
82+
let (secs, nanos) = if exp < -31 {
83+
// the input represents less than 1ns and can not be rounded to it
84+
(0u64, 0u32)
85+
} else if exp < 0 {
86+
// the input is less than 1 second
87+
let t = <$double_ty>::from(mant) << ($offset + exp);
88+
let nanos_offset = $mant_bits + $offset;
89+
let nanos_tmp = u128::from(Nanosecond.per(Second)) * u128::from(t);
90+
let nanos = (nanos_tmp >> nanos_offset) as u32;
91+
92+
let rem_mask = (1 << nanos_offset) - 1;
93+
let rem_msb_mask = 1 << (nanos_offset - 1);
94+
let rem = nanos_tmp & rem_mask;
95+
let is_tie = rem == rem_msb_mask;
96+
let is_even = (nanos & 1) == 0;
97+
let rem_msb = nanos_tmp & rem_msb_mask == 0;
98+
let add_ns = !(rem_msb || (is_even && is_tie));
99+
100+
// f32 does not have enough precision to trigger the second branch
101+
// since it can not represent numbers between 0.999_999_940_395 and 1.0.
102+
let nanos = nanos + add_ns as u32;
103+
if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
104+
(0, nanos)
105+
} else {
106+
(1, 0)
107+
}
108+
} else if exp < $mant_bits {
109+
let secs = u64::from(mant >> ($mant_bits - exp));
110+
let t = <$double_ty>::from((mant << exp) & MANT_MASK);
111+
let nanos_offset = $mant_bits;
112+
let nanos_tmp = <$double_ty>::from(Nanosecond.per(Second)) * t;
113+
let nanos = (nanos_tmp >> nanos_offset) as u32;
114+
115+
let rem_mask = (1 << nanos_offset) - 1;
116+
let rem_msb_mask = 1 << (nanos_offset - 1);
117+
let rem = nanos_tmp & rem_mask;
118+
let is_tie = rem == rem_msb_mask;
119+
let is_even = (nanos & 1) == 0;
120+
let rem_msb = nanos_tmp & rem_msb_mask == 0;
121+
let add_ns = !(rem_msb || (is_even && is_tie));
122+
123+
// f32 does not have enough precision to trigger the second branch.
124+
// For example, it can not represent numbers between 1.999_999_880...
125+
// and 2.0. Bigger values result in even smaller precision of the
126+
// fractional part.
127+
let nanos = nanos + add_ns as u32;
128+
if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
129+
(secs, nanos)
130+
} else {
131+
(secs + 1, 0)
132+
}
133+
} else if exp < 63 {
134+
// Change from std: The exponent here is 63 instead of 64,
135+
// because i64::MAX + 1 is 2^63.
136+
137+
// the input has no fractional part
138+
let secs = u64::from(mant) << (exp - $mant_bits);
139+
(secs, 0)
140+
} else if bits == (i64::MIN as $float_ty).to_bits() {
141+
// Change from std: Signed integers are asymmetrical in that
142+
// iN::MIN is -iN::MAX - 1. So for example i8 covers the
143+
// following numbers -128..=127. The check above (exp < 63)
144+
// doesn't cover i64::MIN as that is -2^63, so we have this
145+
// additional case to handle the asymmetry of iN::MIN.
146+
break 'value Self::new_unchecked(i64::MIN, 0);
147+
} else if $secs.is_nan() {
148+
// Change from std: std doesn't differentiate between the error
149+
// cases.
150+
$is_nan
151+
} else {
152+
$is_overflow
153+
};
154+
155+
// Change from std: All the code is mostly unmodified in that it
156+
// simply calculates an unsigned integer. Here we extract the sign
157+
// bit and assign it to the number. We basically manually do two's
158+
// complement here, we could also use an if and just negate the
159+
// numbers based on the sign, but it turns out to be quite a bit
160+
// slower.
161+
let mask = (bits as $bits_ty_signed) >> ($mant_bits + $exp_bits);
162+
#[allow(trivial_numeric_casts)]
163+
let secs_signed = ((secs as i64) ^ (mask as i64)) - (mask as i64);
164+
#[allow(trivial_numeric_casts)]
165+
let nanos_signed = ((nanos as i32) ^ (mask as i32)) - (mask as i32);
166+
Self::new_unchecked(secs_signed, nanos_signed)
167+
}
168+
}};
169+
}
170+
54171
impl Duration {
55172
// region: constants
56173
/// Equivalent to `0.seconds()`.
@@ -321,17 +438,18 @@ impl Duration {
321438
/// assert_eq!(Duration::seconds_f64(-0.5), -0.5.seconds());
322439
/// ```
323440
pub fn seconds_f64(seconds: f64) -> Self {
324-
if seconds > i64::MAX as f64 || seconds < i64::MIN as f64 {
325-
crate::expect_failed("overflow constructing `time::Duration`");
326-
}
327-
if seconds.is_nan() {
328-
crate::expect_failed("passed NaN to `time::Duration::seconds_f64`");
329-
}
330-
let seconds_truncated = seconds as i64;
331-
// This only works because we handle the overflow condition above.
332-
let nanoseconds =
333-
((seconds - seconds_truncated as f64) * Nanosecond.per(Second) as f64) as i32;
334-
Self::new_unchecked(seconds_truncated, nanoseconds)
441+
try_from_secs!(
442+
secs = seconds,
443+
mantissa_bits = 52,
444+
exponent_bits = 11,
445+
offset = 44,
446+
bits_ty = u64,
447+
bits_ty_signed = i64,
448+
double_ty = u128,
449+
float_ty = f64,
450+
is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f64`"),
451+
is_overflow = crate::expect_failed("overflow constructing `time::Duration`"),
452+
)
335453
}
336454

337455
/// Creates a new `Duration` from the specified number of seconds represented as `f32`.
@@ -342,17 +460,136 @@ impl Duration {
342460
/// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds());
343461
/// ```
344462
pub fn seconds_f32(seconds: f32) -> Self {
345-
if seconds > i64::MAX as f32 || seconds < i64::MIN as f32 {
346-
crate::expect_failed("overflow constructing `time::Duration`");
347-
}
348-
if seconds.is_nan() {
349-
crate::expect_failed("passed NaN to `time::Duration::seconds_f32`");
350-
}
351-
let seconds_truncated = seconds as i64;
352-
// This only works because we handle the overflow condition above.
353-
let nanoseconds =
354-
((seconds - seconds_truncated as f32) * Nanosecond.per(Second) as f32) as i32;
355-
Self::new_unchecked(seconds_truncated, nanoseconds)
463+
try_from_secs!(
464+
secs = seconds,
465+
mantissa_bits = 23,
466+
exponent_bits = 8,
467+
offset = 41,
468+
bits_ty = u32,
469+
bits_ty_signed = i32,
470+
double_ty = u64,
471+
float_ty = f32,
472+
is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f32`"),
473+
is_overflow = crate::expect_failed("overflow constructing `time::Duration`"),
474+
)
475+
}
476+
477+
/// Creates a new `Duration` from the specified number of seconds
478+
/// represented as `f64`. Any values that are out of bounds are saturated at
479+
/// the minimum or maximum respectively. `NaN` gets turned into a `Duration`
480+
/// of 0 seconds.
481+
///
482+
/// ```rust
483+
/// # use time::{Duration, ext::NumericalDuration};
484+
/// assert_eq!(Duration::saturating_seconds_f64(0.5), 0.5.seconds());
485+
/// assert_eq!(Duration::saturating_seconds_f64(-0.5), -0.5.seconds());
486+
/// assert_eq!(Duration::saturating_seconds_f64(f64::NAN), Duration::new(0, 0));
487+
/// assert_eq!(Duration::saturating_seconds_f64(f64::NEG_INFINITY), Duration::MIN);
488+
/// assert_eq!(Duration::saturating_seconds_f64(f64::INFINITY), Duration::MAX);
489+
/// ```
490+
pub fn saturating_seconds_f64(seconds: f64) -> Self {
491+
try_from_secs!(
492+
secs = seconds,
493+
mantissa_bits = 52,
494+
exponent_bits = 11,
495+
offset = 44,
496+
bits_ty = u64,
497+
bits_ty_signed = i64,
498+
double_ty = u128,
499+
float_ty = f64,
500+
is_nan = (0, 0),
501+
is_overflow = return if seconds < 0.0 {
502+
Self::new_unchecked(i64::MIN, -999_999_999)
503+
} else {
504+
Self::new_unchecked(i64::MAX, 999_999_999)
505+
},
506+
)
507+
}
508+
509+
/// Creates a new `Duration` from the specified number of seconds
510+
/// represented as `f32`. Any values that are out of bounds are saturated at
511+
/// the minimum or maximum respectively. `NaN` gets turned into a `Duration`
512+
/// of 0 seconds.
513+
///
514+
/// ```rust
515+
/// # use time::{Duration, ext::NumericalDuration};
516+
/// assert_eq!(Duration::saturating_seconds_f32(0.5), 0.5.seconds());
517+
/// assert_eq!(Duration::saturating_seconds_f32(-0.5), (-0.5).seconds());
518+
/// assert_eq!(Duration::saturating_seconds_f32(f32::NAN), Duration::new(0, 0));
519+
/// assert_eq!(Duration::saturating_seconds_f32(f32::NEG_INFINITY), Duration::MIN);
520+
/// assert_eq!(Duration::saturating_seconds_f32(f32::INFINITY), Duration::MAX);
521+
/// ```
522+
pub fn saturating_seconds_f32(seconds: f32) -> Self {
523+
try_from_secs!(
524+
secs = seconds,
525+
mantissa_bits = 23,
526+
exponent_bits = 8,
527+
offset = 41,
528+
bits_ty = u32,
529+
bits_ty_signed = i32,
530+
double_ty = u64,
531+
float_ty = f32,
532+
is_nan = (0, 0),
533+
is_overflow = return if seconds < 0.0 {
534+
Self::new_unchecked(i64::MIN, -999_999_999)
535+
} else {
536+
Self::new_unchecked(i64::MAX, 999_999_999)
537+
},
538+
)
539+
}
540+
541+
/// Creates a new `Duration` from the specified number of seconds
542+
/// represented as `f64`. Returns `None` if the `Duration` can't be
543+
/// represented.
544+
///
545+
/// ```rust
546+
/// # use time::{Duration, ext::NumericalDuration};
547+
/// assert_eq!(Duration::checked_seconds_f64(0.5), Some(0.5.seconds()));
548+
/// assert_eq!(Duration::checked_seconds_f64(-0.5), Some(-0.5.seconds()));
549+
/// assert_eq!(Duration::checked_seconds_f64(f64::NAN), None);
550+
/// assert_eq!(Duration::checked_seconds_f64(f64::NEG_INFINITY), None);
551+
/// assert_eq!(Duration::checked_seconds_f64(f64::INFINITY), None);
552+
/// ```
553+
pub fn checked_seconds_f64(seconds: f64) -> Option<Self> {
554+
Some(try_from_secs!(
555+
secs = seconds,
556+
mantissa_bits = 52,
557+
exponent_bits = 11,
558+
offset = 44,
559+
bits_ty = u64,
560+
bits_ty_signed = i64,
561+
double_ty = u128,
562+
float_ty = f64,
563+
is_nan = return None,
564+
is_overflow = return None,
565+
))
566+
}
567+
568+
/// Creates a new `Duration` from the specified number of seconds
569+
/// represented as `f32`. Returns `None` if the `Duration` can't be
570+
/// represented.
571+
///
572+
/// ```rust
573+
/// # use time::{Duration, ext::NumericalDuration};
574+
/// assert_eq!(Duration::checked_seconds_f32(0.5), Some(0.5.seconds()));
575+
/// assert_eq!(Duration::checked_seconds_f32(-0.5), Some(-0.5.seconds()));
576+
/// assert_eq!(Duration::checked_seconds_f32(f32::NAN), None);
577+
/// assert_eq!(Duration::checked_seconds_f32(f32::NEG_INFINITY), None);
578+
/// assert_eq!(Duration::checked_seconds_f32(f32::INFINITY), None);
579+
/// ```
580+
pub fn checked_seconds_f32(seconds: f32) -> Option<Self> {
581+
Some(try_from_secs!(
582+
secs = seconds,
583+
mantissa_bits = 23,
584+
exponent_bits = 8,
585+
offset = 41,
586+
bits_ty = u32,
587+
bits_ty_signed = i32,
588+
double_ty = u64,
589+
float_ty = f32,
590+
is_nan = return None,
591+
is_overflow = return None,
592+
))
356593
}
357594

358595
/// Create a new `Duration` with the given number of milliseconds.

0 commit comments

Comments
 (0)