Skip to content

Commit 48a261c

Browse files
committed
Improve precision of Duration-float operations
Rather than convert Duration to a float in order to multiply or divide by a floating point number, which entails a loss of precision, we instead represent both operands as u128 nanoseconds and perform integer arithmetic thereupon.
1 parent d96ec37 commit 48a261c

File tree

2 files changed

+238
-10
lines changed

2 files changed

+238
-10
lines changed

library/core/src/time.rs

Lines changed: 207 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
//! assert_eq!(total, Duration::new(10, 7));
2020
//! ```
2121
22-
use crate::fmt;
2322
use crate::iter::Sum;
2423
use crate::num::niche_types::Nanoseconds;
2524
use crate::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
25+
use crate::{fmt, hint};
2626

2727
const NANOS_PER_SEC: u32 = 1_000_000_000;
2828
const NANOS_PER_MILLI: u32 = 1_000_000;
@@ -1018,7 +1018,79 @@ impl Duration {
10181018
without modifying the original"]
10191019
#[inline]
10201020
pub fn mul_f64(self, rhs: f64) -> Duration {
1021-
Duration::from_secs_f64(rhs * self.as_secs_f64())
1021+
if rhs < 0.0 {
1022+
// Ideally we'd always panic here, however we wish to preserve the stabilized behavior.
1023+
if self.as_secs_f64() * rhs != 0.0 {
1024+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::Negative });
1025+
} else {
1026+
return Duration::ZERO;
1027+
}
1028+
}
1029+
if !rhs.is_finite() {
1030+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan });
1031+
}
1032+
1033+
let lhs_nanos = self.as_nanos();
1034+
if lhs_nanos == 0 || rhs == 0.0 {
1035+
return Duration::ZERO;
1036+
}
1037+
1038+
let lhs_log2 = lhs_nanos.ilog2() as i32;
1039+
let (rhs_significand, rhs_log2) = f64_normalize(rhs);
1040+
1041+
let prod_log2_floor = lhs_log2 + rhs_log2;
1042+
if prod_log2_floor > Duration::MAX.as_nanos().ilog2() as i32 {
1043+
// Product is definitely more than Duration::MAX
1044+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan });
1045+
}
1046+
1047+
let prod_log2_ceil = prod_log2_floor + 1;
1048+
if prod_log2_ceil < -1 {
1049+
// Product is definitely less than 0.5ns
1050+
return Duration::ZERO;
1051+
}
1052+
1053+
// Denormalize to a purely integer significand.
1054+
let exp = rhs_log2 + 1 - f64::MANTISSA_DIGITS as i32;
1055+
let rhs_significand = rhs_significand as u128;
1056+
1057+
let nanos = if exp >= 0 {
1058+
// `rhs` has no fractional precision, so mere integer multiplication is sufficient.
1059+
(lhs_nanos * rhs_significand) << exp
1060+
} else {
1061+
// Perform long-multiplication to calculate the 2 most significant 128-bit chunks of the
1062+
// product: hi (most significant) and lo (least significant).
1063+
let (lo, hi) = lhs_nanos.carrying_mul(rhs_significand, 0);
1064+
1065+
let nexp = -exp as u32;
1066+
1067+
if nexp < 128 {
1068+
// The result spans the two chunks, hi and lo; shift their concatenation down.
1069+
// The rounding bit is the most significant from lo that was shifted out of the
1070+
// result.
1071+
debug_assert!(nexp > 128 - hi.leading_zeros());
1072+
let round_up = (lo >> (nexp - 1)) & 1;
1073+
hi.funnel_shr(lo, nexp) + round_up
1074+
} else {
1075+
// The result is entirely within the most significant chunk, hi; implicitly shift
1076+
// down by discarding the least significant chunk, lo.
1077+
let shift = nexp - 128;
1078+
let round_up = if let Some(one_shift_less) = shift.checked_sub(1) {
1079+
// Bits were shifted from hi; the most significant of them is the rounding bit.
1080+
(hi >> one_shift_less) & 1
1081+
} else {
1082+
// No shift; the most significant bit from discarded lo is the rounding bit.
1083+
lo >> 127
1084+
};
1085+
(hi >> shift) + round_up
1086+
}
1087+
};
1088+
1089+
if nanos > Duration::MAX.as_nanos() {
1090+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan });
1091+
}
1092+
1093+
Duration::from_nanos_u128(nanos)
10221094
}
10231095

10241096
/// Multiplies `Duration` by `f32`.
@@ -1031,15 +1103,15 @@ impl Duration {
10311103
/// use std::time::Duration;
10321104
///
10331105
/// let dur = Duration::new(2, 700_000_000);
1034-
/// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_641));
1106+
/// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_283));
10351107
/// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847_800, 0));
10361108
/// ```
10371109
#[stable(feature = "duration_float", since = "1.38.0")]
10381110
#[must_use = "this returns the result of the operation, \
10391111
without modifying the original"]
10401112
#[inline]
10411113
pub fn mul_f32(self, rhs: f32) -> Duration {
1042-
Duration::from_secs_f32(rhs * self.as_secs_f32())
1114+
self.mul_f64(rhs.into())
10431115
}
10441116

10451117
/// Divides `Duration` by `f64`.
@@ -1060,7 +1132,103 @@ impl Duration {
10601132
without modifying the original"]
10611133
#[inline]
10621134
pub fn div_f64(self, rhs: f64) -> Duration {
1063-
Duration::from_secs_f64(self.as_secs_f64() / rhs)
1135+
if rhs.is_finite() && rhs.is_sign_negative() {
1136+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::Negative });
1137+
}
1138+
if rhs.is_nan() || rhs == 0.0 {
1139+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan });
1140+
}
1141+
1142+
let lhs_nanos = self.as_nanos();
1143+
if lhs_nanos == 0 || rhs.is_infinite() {
1144+
return Duration::ZERO;
1145+
}
1146+
1147+
let lhs_log2 = lhs_nanos.ilog2() as i32;
1148+
let (rhs_significand, rhs_log2) = f64_normalize(rhs);
1149+
1150+
// Avoid needless div-by-zero checks.
1151+
// SAFETY: if `rhs` is normal, the implied bit will have been set in `rhs_significand`;
1152+
// if it is subnormal, then the mantissa cannot be zero (we already panicked above
1153+
// in the event that `rhs` is NaN or zero, and early-returned in the event that it
1154+
// is infinite).
1155+
unsafe {
1156+
hint::assert_unchecked(rhs_significand != 0);
1157+
}
1158+
1159+
let quot_log2_ceil = lhs_log2 - rhs_log2;
1160+
if quot_log2_ceil < -1 {
1161+
// Quotient is definitely less than 0.5ns
1162+
return Duration::ZERO;
1163+
}
1164+
1165+
// To assist the compiler in eliminating the bounds check on funnel_shl, The following `if`
1166+
// condition has been manually expanded & rewritten from:
1167+
// let quot_log2_floor = quot_log2_ceil - 1;
1168+
// if quot_log2_floor > Duration::MAX.as_nanos().ilog2() { ... }
1169+
if rhs_log2 < lhs_log2 - Duration::MAX.as_nanos().ilog2() as i32 - 1 {
1170+
// Quotient is definitely more than Duration::MAX
1171+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan });
1172+
}
1173+
1174+
// Denormalize to a purely integer significand.
1175+
let exp = rhs_log2 + 1 - f64::MANTISSA_DIGITS as i32;
1176+
let rhs_significand = rhs_significand as u128;
1177+
1178+
let nanos = if let Ok(shift) = u32::try_from(exp) {
1179+
// `rhs` has no fractional precision, so mere integer division is sufficient.
1180+
let unshifted_q = lhs_nanos / rhs_significand;
1181+
let round_up = if let Some(one_shift_less) = shift.checked_sub(1) {
1182+
// Bits are to be shifted; the most significant of them is the rounding bit.
1183+
(unshifted_q >> one_shift_less) & 1
1184+
} else {
1185+
// Nothing is to be shifted out, there is nothing to round.
1186+
0
1187+
};
1188+
(unshifted_q >> shift) + round_up
1189+
} else {
1190+
// Calculate the first 127 bits of the significand's reciprocal, and any remainder.
1191+
const RECIP_N: u128 = u128::rotate_right(1, 1);
1192+
let recip_q = RECIP_N / rhs_significand;
1193+
let recip_r = RECIP_N % rhs_significand;
1194+
1195+
// Calculate the next 127 bits of the reciprocal, and shift them into the most
1196+
// significant bits of a u128. Whilst the least significant bit may be incorrect,
1197+
// it is immaterial to the result.
1198+
let recip_f = ((recip_q * recip_r) + (recip_r * recip_r) / rhs_significand) << 1;
1199+
1200+
// Long-multiply lhs with the reciprocal to calculate the 3 most significant 128-bit
1201+
// chunks of the quotient: q2 (most significant) to q0 (least significant).
1202+
let (q0, carry) = lhs_nanos.carrying_mul(recip_f, 0);
1203+
let (q1, q2) = lhs_nanos.carrying_mul(recip_q, carry);
1204+
1205+
// Offset exponent by 1 because reciprocal is calculated as 2^127/significand,
1206+
// rather than 2^128/significand (which obviously requires greater width than u128).
1207+
let nexp = (1 - exp) as u32;
1208+
1209+
let (hi, lo, shift) = if nexp < 128 {
1210+
// The result spans the most significant two chunks, q2 and q1; their concatenation
1211+
// simply needs to be shifted by `nexp` bits.
1212+
(q2, q1, nexp)
1213+
} else {
1214+
// The result spans the least significant two chunks, q1 and q0: implicitly shift up
1215+
// by discarding the most signiciant chunk q2; the resulting concatenation then
1216+
// simply needs to be shifted by `nexp - 128` bits.
1217+
debug_assert_eq!(q2, 0);
1218+
(q1, q0, nexp - 128)
1219+
};
1220+
1221+
debug_assert!(shift <= hi.leading_zeros());
1222+
1223+
let round_up = (lo >> (127 - shift)) & 1;
1224+
hi.funnel_shl(lo, shift) + round_up
1225+
};
1226+
1227+
if nanos > Duration::MAX.as_nanos() {
1228+
panic!("{}", TryFromFloatSecsError { kind: TryFromFloatSecsErrorKind::OverflowOrNan });
1229+
}
1230+
1231+
Duration::from_nanos_u128(nanos)
10641232
}
10651233

10661234
/// Divides `Duration` by `f32`.
@@ -1075,15 +1243,15 @@ impl Duration {
10751243
/// let dur = Duration::new(2, 700_000_000);
10761244
/// // note that due to rounding errors result is slightly
10771245
/// // different from 0.859_872_611
1078-
/// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_580));
1246+
/// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_583));
10791247
/// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_599));
10801248
/// ```
10811249
#[stable(feature = "duration_float", since = "1.38.0")]
10821250
#[must_use = "this returns the result of the operation, \
10831251
without modifying the original"]
10841252
#[inline]
10851253
pub fn div_f32(self, rhs: f32) -> Duration {
1086-
Duration::from_secs_f32(self.as_secs_f32() / rhs)
1254+
self.div_f64(rhs.into())
10871255
}
10881256

10891257
/// Divides `Duration` by `Duration` and returns `f64`.
@@ -1780,3 +1948,35 @@ impl Duration {
17801948
)
17811949
}
17821950
}
1951+
1952+
/// For the input `f`, returns its `(normalized_significand, exponent)` pair where the
1953+
/// `normalized_significand` is a `f64::MANTISSA_DIGITS`-bit fixed point number in [0b1, 0b10) -
1954+
/// and therefore its `f64::MANTISSA_DIGITS`th bit is always set and is always its most significant
1955+
/// set bit; and where the `exponent` is the base-2 exponent of that `normalized_significand` such
1956+
/// that, together, they evaluate to `f`.
1957+
///
1958+
/// The return values are unspecified if `f` is neither normal nor subnormal (ie is zero, NaN or
1959+
/// infinite).
1960+
fn f64_normalize(f: f64) -> (u64, i32) {
1961+
const NUM_BITS_MANT: u32 = f64::MANTISSA_DIGITS - 1;
1962+
const NUM_BITS_EXP: u32 = 64 - f64::MANTISSA_DIGITS;
1963+
1964+
const MANT_IMPLIED_BIT: u64 = 1 << NUM_BITS_MANT;
1965+
const MANT_MASK: u64 = MANT_IMPLIED_BIT - 1;
1966+
const EXP_MASK: u64 = ((1 << NUM_BITS_EXP) - 1) << NUM_BITS_MANT;
1967+
const EXP_BIAS: i32 = 1 - f64::MAX_EXP;
1968+
1969+
let raw = f.to_bits();
1970+
let mantissa = raw & MANT_MASK;
1971+
let (significand, biased_exp) = if f.is_normal() {
1972+
let biased_exp = ((raw & EXP_MASK) >> NUM_BITS_MANT) as i32;
1973+
(mantissa | MANT_IMPLIED_BIT, biased_exp)
1974+
} else {
1975+
// f is subnormal: renormalize by shifting so that the highest set bit is in the correct
1976+
// leading bit position.
1977+
let shift = mantissa.leading_zeros() - NUM_BITS_EXP;
1978+
(mantissa << shift, 1 - shift as i32)
1979+
};
1980+
1981+
(significand, biased_exp + EXP_BIAS)
1982+
}

library/coretests/tests/time.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -642,15 +642,15 @@ fn duration_fp_div_negative() {
642642
}
643643

644644
const TOO_LARGE_FACTOR: f64 = Duration::MAX.as_nanos() as f64;
645-
const TOO_LARGE_DIVISOR: f64 = (Duration::MAX.as_secs_f64() * 2e9).next_up();
646-
const SMALLEST_DIVISOR: f64 = (TOO_LARGE_DIVISOR.recip() * 2.0).next_up().next_up();
645+
const TOO_LARGE_DIVISOR: f64 = TOO_LARGE_FACTOR * 2.0;
646+
const SMALLEST_DIVISOR: f64 = TOO_LARGE_DIVISOR.recip() * 2.0;
647647
const SMALLEST_FACTOR: f64 = TOO_LARGE_FACTOR.recip() / 2.0;
648648
const SMALLEST_NEGFACTOR: f64 = (0.0f64.next_down() * 0.5e9).next_up();
649649

650650
#[test]
651651
fn duration_fp_boundaries() {
652652
const DURATION_BITS: u32 = Duration::MAX.as_nanos().ilog2() + 1;
653-
const PRECISION: u32 = DURATION_BITS - f64::MANTISSA_DIGITS + 1;
653+
const PRECISION: u32 = DURATION_BITS - f64::MANTISSA_DIGITS;
654654

655655
assert_eq!(Duration::MAX.mul_f64(0.0), Duration::ZERO);
656656
assert_eq!(Duration::MAX.mul_f64(-0.0), Duration::ZERO);
@@ -695,3 +695,31 @@ fn duration_fp_mul_overflow() {
695695
fn duration_fp_div_overflow() {
696696
let _ = Duration::NANOSECOND.div_f64(SMALLEST_DIVISOR.next_down());
697697
}
698+
699+
#[test]
700+
fn precise_duration_fp_mul() {
701+
let d1 = Duration::from_nanos_u128(1 << 90);
702+
let d2 = Duration::from_nanos_u128(2 << 90);
703+
let d3 = Duration::from_nanos_u128(3 << 90);
704+
705+
assert_eq!(d1.mul_f32(1.0), d1);
706+
assert_eq!(d1.mul_f32(2.0), d2);
707+
assert_eq!(d1.mul_f32(3.0), d3);
708+
assert_eq!(d2.mul_f32(1.5), d3);
709+
710+
let _ = Duration::MAX.mul_f32(1.0);
711+
}
712+
713+
#[test]
714+
fn precise_duration_fp_div() {
715+
let d1 = Duration::from_nanos_u128(1 << 90);
716+
let d2 = Duration::from_nanos_u128(2 << 90);
717+
let d3 = Duration::from_nanos_u128(3 << 90);
718+
719+
assert_eq!(d1.div_f32(1.0), d1);
720+
assert_eq!(d2.div_f32(2.0), d1);
721+
assert_eq!(d3.div_f32(3.0), d1);
722+
assert_eq!(d3.div_f32(1.5), d2);
723+
724+
let _ = Duration::MAX.div_f32(1.0);
725+
}

0 commit comments

Comments
 (0)