Skip to content

Commit 97ac8d9

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 97ac8d9

File tree

1 file changed

+278
-22
lines changed

1 file changed

+278
-22
lines changed

time/src/duration.rs

Lines changed: 278 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,124 @@ 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+
#[rustfmt::skip] // Skip `rustfmt` because it reformats the arguments of the macro weirdly.
59+
macro_rules! try_from_secs {
60+
(
61+
secs = $secs: expr,
62+
mantissa_bits = $mant_bits: literal,
63+
exponent_bits = $exp_bits: literal,
64+
offset = $offset: literal,
65+
bits_ty = $bits_ty:ty,
66+
bits_ty_signed = $bits_ty_signed:ty,
67+
double_ty = $double_ty:ty,
68+
float_ty = $float_ty:ty,
69+
is_nan = $is_nan:expr,
70+
is_overflow = $is_overflow:expr,
71+
) => {{
72+
'value: {
73+
const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2;
74+
const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1;
75+
const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1;
76+
77+
// Change from std: No error check for negative values necessary.
78+
79+
let bits = $secs.to_bits();
80+
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
81+
let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP;
82+
83+
let (secs, nanos) = if exp < -31 {
84+
// the input represents less than 1ns and can not be rounded to it
85+
(0u64, 0u32)
86+
} else if exp < 0 {
87+
// the input is less than 1 second
88+
let t = <$double_ty>::from(mant) << ($offset + exp);
89+
let nanos_offset = $mant_bits + $offset;
90+
let nanos_tmp = u128::from(Nanosecond.per(Second)) * u128::from(t);
91+
let nanos = (nanos_tmp >> nanos_offset) as u32;
92+
93+
let rem_mask = (1 << nanos_offset) - 1;
94+
let rem_msb_mask = 1 << (nanos_offset - 1);
95+
let rem = nanos_tmp & rem_mask;
96+
let is_tie = rem == rem_msb_mask;
97+
let is_even = (nanos & 1) == 0;
98+
let rem_msb = nanos_tmp & rem_msb_mask == 0;
99+
let add_ns = !(rem_msb || (is_even && is_tie));
100+
101+
// f32 does not have enough precision to trigger the second branch
102+
// since it can not represent numbers between 0.999_999_940_395 and 1.0.
103+
let nanos = nanos + add_ns as u32;
104+
if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
105+
(0, nanos)
106+
} else {
107+
(1, 0)
108+
}
109+
} else if exp < $mant_bits {
110+
let secs = u64::from(mant >> ($mant_bits - exp));
111+
let t = <$double_ty>::from((mant << exp) & MANT_MASK);
112+
let nanos_offset = $mant_bits;
113+
let nanos_tmp = <$double_ty>::from(Nanosecond.per(Second)) * t;
114+
let nanos = (nanos_tmp >> nanos_offset) as u32;
115+
116+
let rem_mask = (1 << nanos_offset) - 1;
117+
let rem_msb_mask = 1 << (nanos_offset - 1);
118+
let rem = nanos_tmp & rem_mask;
119+
let is_tie = rem == rem_msb_mask;
120+
let is_even = (nanos & 1) == 0;
121+
let rem_msb = nanos_tmp & rem_msb_mask == 0;
122+
let add_ns = !(rem_msb || (is_even && is_tie));
123+
124+
// f32 does not have enough precision to trigger the second branch.
125+
// For example, it can not represent numbers between 1.999_999_880...
126+
// and 2.0. Bigger values result in even smaller precision of the
127+
// fractional part.
128+
let nanos = nanos + add_ns as u32;
129+
if ($mant_bits == 23) || (nanos != Nanosecond.per(Second)) {
130+
(secs, nanos)
131+
} else {
132+
(secs + 1, 0)
133+
}
134+
} else if exp < 63 {
135+
// Change from std: The exponent here is 63 instead of 64,
136+
// because i64::MAX + 1 is 2^63.
137+
138+
// the input has no fractional part
139+
let secs = u64::from(mant) << (exp - $mant_bits);
140+
(secs, 0)
141+
} else if bits == (i64::MIN as $float_ty).to_bits() {
142+
// Change from std: Signed integers are asymmetrical in that
143+
// iN::MIN is -iN::MAX - 1. So for example i8 covers the
144+
// following numbers -128..=127. The check above (exp < 63)
145+
// doesn't cover i64::MIN as that is -2^63, so we have this
146+
// additional case to handle the asymmetry of iN::MIN.
147+
break 'value Self::new_unchecked(i64::MIN, 0);
148+
} else if $secs.is_nan() {
149+
// Change from std: std doesn't differentiate between the error
150+
// cases.
151+
$is_nan
152+
} else {
153+
$is_overflow
154+
};
155+
156+
// Change from std: All the code is mostly unmodified in that it
157+
// simply calculates an unsigned integer. Here we extract the sign
158+
// bit and assign it to the number. We basically manually do two's
159+
// complement here, we could also use an if and just negate the
160+
// numbers based on the sign, but it turns out to be quite a bit
161+
// slower.
162+
let mask = (bits as $bits_ty_signed) >> ($mant_bits + $exp_bits);
163+
#[allow(trivial_numeric_casts)]
164+
let secs_signed = ((secs as i64) ^ (mask as i64)) - (mask as i64);
165+
#[allow(trivial_numeric_casts)]
166+
let nanos_signed = ((nanos as i32) ^ (mask as i32)) - (mask as i32);
167+
Self::new_unchecked(secs_signed, nanos_signed)
168+
}
169+
}};
170+
}
171+
54172
impl Duration {
55173
// region: constants
56174
/// Equivalent to `0.seconds()`.
@@ -321,17 +439,18 @@ impl Duration {
321439
/// assert_eq!(Duration::seconds_f64(-0.5), -0.5.seconds());
322440
/// ```
323441
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)
442+
try_from_secs!(
443+
secs = seconds,
444+
mantissa_bits = 52,
445+
exponent_bits = 11,
446+
offset = 44,
447+
bits_ty = u64,
448+
bits_ty_signed = i64,
449+
double_ty = u128,
450+
float_ty = f64,
451+
is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f64`"),
452+
is_overflow = crate::expect_failed("overflow constructing `time::Duration`"),
453+
)
335454
}
336455

337456
/// Creates a new `Duration` from the specified number of seconds represented as `f32`.
@@ -342,17 +461,154 @@ impl Duration {
342461
/// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds());
343462
/// ```
344463
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)
464+
try_from_secs!(
465+
secs = seconds,
466+
mantissa_bits = 23,
467+
exponent_bits = 8,
468+
offset = 41,
469+
bits_ty = u32,
470+
bits_ty_signed = i32,
471+
double_ty = u64,
472+
float_ty = f32,
473+
is_nan = crate::expect_failed("passed NaN to `time::Duration::seconds_f32`"),
474+
is_overflow = crate::expect_failed("overflow constructing `time::Duration`"),
475+
)
476+
}
477+
478+
/// Creates a new `Duration` from the specified number of seconds
479+
/// represented as `f64`. Any values that are out of bounds are saturated at
480+
/// the minimum or maximum respectively. `NaN` gets turned into a `Duration`
481+
/// of 0 seconds.
482+
///
483+
/// ```rust
484+
/// # use time::{Duration, ext::NumericalDuration};
485+
/// assert_eq!(Duration::saturating_seconds_f64(0.5), 0.5.seconds());
486+
/// assert_eq!(Duration::saturating_seconds_f64(-0.5), -0.5.seconds());
487+
/// assert_eq!(
488+
/// Duration::saturating_seconds_f64(f64::NAN),
489+
/// Duration::new(0, 0),
490+
/// );
491+
/// assert_eq!(
492+
/// Duration::saturating_seconds_f64(f64::NEG_INFINITY),
493+
/// Duration::MIN,
494+
/// );
495+
/// assert_eq!(
496+
/// Duration::saturating_seconds_f64(f64::INFINITY),
497+
/// Duration::MAX,
498+
/// );
499+
/// ```
500+
pub fn saturating_seconds_f64(seconds: f64) -> Self {
501+
try_from_secs!(
502+
secs = seconds,
503+
mantissa_bits = 52,
504+
exponent_bits = 11,
505+
offset = 44,
506+
bits_ty = u64,
507+
bits_ty_signed = i64,
508+
double_ty = u128,
509+
float_ty = f64,
510+
is_nan = (0, 0),
511+
is_overflow = return if seconds < 0.0 {
512+
Self::new_unchecked(i64::MIN, -999_999_999)
513+
} else {
514+
Self::new_unchecked(i64::MAX, 999_999_999)
515+
},
516+
)
517+
}
518+
519+
/// Creates a new `Duration` from the specified number of seconds
520+
/// represented as `f32`. Any values that are out of bounds are saturated at
521+
/// the minimum or maximum respectively. `NaN` gets turned into a `Duration`
522+
/// of 0 seconds.
523+
///
524+
/// ```rust
525+
/// # use time::{Duration, ext::NumericalDuration};
526+
/// assert_eq!(Duration::saturating_seconds_f32(0.5), 0.5.seconds());
527+
/// assert_eq!(Duration::saturating_seconds_f32(-0.5), (-0.5).seconds());
528+
/// assert_eq!(
529+
/// Duration::saturating_seconds_f32(f32::NAN),
530+
/// Duration::new(0, 0),
531+
/// );
532+
/// assert_eq!(
533+
/// Duration::saturating_seconds_f32(f32::NEG_INFINITY),
534+
/// Duration::MIN,
535+
/// );
536+
/// assert_eq!(
537+
/// Duration::saturating_seconds_f32(f32::INFINITY),
538+
/// Duration::MAX,
539+
/// );
540+
/// ```
541+
pub fn saturating_seconds_f32(seconds: f32) -> Self {
542+
try_from_secs!(
543+
secs = seconds,
544+
mantissa_bits = 23,
545+
exponent_bits = 8,
546+
offset = 41,
547+
bits_ty = u32,
548+
bits_ty_signed = i32,
549+
double_ty = u64,
550+
float_ty = f32,
551+
is_nan = (0, 0),
552+
is_overflow = return if seconds < 0.0 {
553+
Self::new_unchecked(i64::MIN, -999_999_999)
554+
} else {
555+
Self::new_unchecked(i64::MAX, 999_999_999)
556+
},
557+
)
558+
}
559+
560+
/// Creates a new `Duration` from the specified number of seconds
561+
/// represented as `f64`. Returns `None` if the `Duration` can't be
562+
/// represented.
563+
///
564+
/// ```rust
565+
/// # use time::{Duration, ext::NumericalDuration};
566+
/// assert_eq!(Duration::checked_seconds_f64(0.5), Some(0.5.seconds()));
567+
/// assert_eq!(Duration::checked_seconds_f64(-0.5), Some(-0.5.seconds()));
568+
/// assert_eq!(Duration::checked_seconds_f64(f64::NAN), None);
569+
/// assert_eq!(Duration::checked_seconds_f64(f64::NEG_INFINITY), None);
570+
/// assert_eq!(Duration::checked_seconds_f64(f64::INFINITY), None);
571+
/// ```
572+
pub fn checked_seconds_f64(seconds: f64) -> Option<Self> {
573+
Some(try_from_secs!(
574+
secs = seconds,
575+
mantissa_bits = 52,
576+
exponent_bits = 11,
577+
offset = 44,
578+
bits_ty = u64,
579+
bits_ty_signed = i64,
580+
double_ty = u128,
581+
float_ty = f64,
582+
is_nan = return None,
583+
is_overflow = return None,
584+
))
585+
}
586+
587+
/// Creates a new `Duration` from the specified number of seconds
588+
/// represented as `f32`. Returns `None` if the `Duration` can't be
589+
/// represented.
590+
///
591+
/// ```rust
592+
/// # use time::{Duration, ext::NumericalDuration};
593+
/// assert_eq!(Duration::checked_seconds_f32(0.5), Some(0.5.seconds()));
594+
/// assert_eq!(Duration::checked_seconds_f32(-0.5), Some(-0.5.seconds()));
595+
/// assert_eq!(Duration::checked_seconds_f32(f32::NAN), None);
596+
/// assert_eq!(Duration::checked_seconds_f32(f32::NEG_INFINITY), None);
597+
/// assert_eq!(Duration::checked_seconds_f32(f32::INFINITY), None);
598+
/// ```
599+
pub fn checked_seconds_f32(seconds: f32) -> Option<Self> {
600+
Some(try_from_secs!(
601+
secs = seconds,
602+
mantissa_bits = 23,
603+
exponent_bits = 8,
604+
offset = 41,
605+
bits_ty = u32,
606+
bits_ty_signed = i32,
607+
double_ty = u64,
608+
float_ty = f32,
609+
is_nan = return None,
610+
is_overflow = return None,
611+
))
356612
}
357613

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

0 commit comments

Comments
 (0)