Skip to content

Commit e0bcf77

Browse files
committed
Improve Duration::try_from_secs_f32/64 accuracy by directly processing exponent and mantissa
1 parent 788b1fe commit e0bcf77

File tree

3 files changed

+202
-103
lines changed

3 files changed

+202
-103
lines changed

library/core/src/time.rs

Lines changed: 200 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -711,14 +711,28 @@ impl Duration {
711711
/// as `f64`.
712712
///
713713
/// # Panics
714-
/// This constructor will panic if `secs` is not finite, negative or overflows `Duration`.
714+
/// This constructor will panic if `secs` is negative, overflows `Duration` or not finite.
715715
///
716716
/// # Examples
717717
/// ```
718718
/// use std::time::Duration;
719719
///
720-
/// let dur = Duration::from_secs_f64(2.7);
721-
/// assert_eq!(dur, Duration::new(2, 700_000_000));
720+
/// let res = Duration::from_secs_f64(0.0);
721+
/// assert_eq!(res, Duration::new(0, 0));
722+
/// let res = Duration::from_secs_f64(1e-20);
723+
/// assert_eq!(res, Duration::new(0, 0));
724+
/// let res = Duration::from_secs_f64(4.2e-7);
725+
/// assert_eq!(res, Duration::new(0, 420));
726+
/// let res = Duration::from_secs_f64(2.7);
727+
/// assert_eq!(res, Duration::new(2, 700_000_000));
728+
/// let res = Duration::from_secs_f64(3e10);
729+
/// assert_eq!(res, Duration::new(30_000_000_000, 0));
730+
/// // subnormal float
731+
/// let res = Duration::from_secs_f64(f64::from_bits(1));
732+
/// assert_eq!(res, Duration::new(0, 0));
733+
/// // conversion uses truncation, not rounding
734+
/// let res = Duration::from_secs_f64(0.999e-9);
735+
/// assert_eq!(res, Duration::new(0, 0));
722736
/// ```
723737
#[stable(feature = "duration_float", since = "1.38.0")]
724738
#[must_use]
@@ -731,55 +745,32 @@ impl Duration {
731745
}
732746
}
733747

734-
/// The checked version of [`from_secs_f64`].
735-
///
736-
/// [`from_secs_f64`]: Duration::from_secs_f64
737-
///
738-
/// This constructor will return an `Err` if `secs` is not finite, negative or overflows `Duration`.
739-
///
740-
/// # Examples
741-
/// ```
742-
/// #![feature(duration_checked_float)]
743-
/// use std::time::Duration;
744-
///
745-
/// let dur = Duration::try_from_secs_f64(2.7);
746-
/// assert_eq!(dur, Ok(Duration::new(2, 700_000_000)));
747-
///
748-
/// let negative = Duration::try_from_secs_f64(-5.0);
749-
/// assert!(negative.is_err());
750-
/// ```
751-
#[unstable(feature = "duration_checked_float", issue = "83400")]
752-
#[inline]
753-
pub const fn try_from_secs_f64(secs: f64) -> Result<Duration, FromSecsError> {
754-
const MAX_NANOS_F64: f64 = ((u64::MAX as u128 + 1) * (NANOS_PER_SEC as u128)) as f64;
755-
let nanos = secs * (NANOS_PER_SEC as f64);
756-
if !nanos.is_finite() {
757-
Err(FromSecsError { kind: FromSecsErrorKind::NonFinite })
758-
} else if nanos >= MAX_NANOS_F64 {
759-
Err(FromSecsError { kind: FromSecsErrorKind::Overflow })
760-
} else if nanos < 0.0 {
761-
Err(FromSecsError { kind: FromSecsErrorKind::Negative })
762-
} else {
763-
let nanos = nanos as u128;
764-
Ok(Duration {
765-
secs: (nanos / (NANOS_PER_SEC as u128)) as u64,
766-
nanos: (nanos % (NANOS_PER_SEC as u128)) as u32,
767-
})
768-
}
769-
}
770-
771748
/// Creates a new `Duration` from the specified number of seconds represented
772749
/// as `f32`.
773750
///
774751
/// # Panics
775-
/// This constructor will panic if `secs` is not finite, negative or overflows `Duration`.
752+
/// This constructor will panic if `secs` is negative, overflows `Duration` or not finite.
776753
///
777754
/// # Examples
778755
/// ```
779756
/// use std::time::Duration;
780757
///
781-
/// let dur = Duration::from_secs_f32(2.7);
782-
/// assert_eq!(dur, Duration::new(2, 700_000_000));
758+
/// let res = Duration::from_secs_f32(0.0);
759+
/// assert_eq!(res, Duration::new(0, 0));
760+
/// let res = Duration::from_secs_f32(1e-20);
761+
/// assert_eq!(res, Duration::new(0, 0));
762+
/// let res = Duration::from_secs_f32(4.2e-7);
763+
/// assert_eq!(res, Duration::new(0, 419));
764+
/// let res = Duration::from_secs_f32(2.7);
765+
/// assert_eq!(res, Duration::new(2, 700_000_047));
766+
/// let res = Duration::from_secs_f32(3e10);
767+
/// assert_eq!(res, Duration::new(30_000_001_024, 0));
768+
/// // subnormal float
769+
/// let res = Duration::from_secs_f32(f32::from_bits(1));
770+
/// assert_eq!(res, Duration::new(0, 0));
771+
/// // conversion uses truncation, not rounding
772+
/// let res = Duration::from_secs_f32(0.999e-9);
773+
/// assert_eq!(res, Duration::new(0, 0));
783774
/// ```
784775
#[stable(feature = "duration_float", since = "1.38.0")]
785776
#[must_use]
@@ -792,47 +783,10 @@ impl Duration {
792783
}
793784
}
794785

795-
/// The checked version of [`from_secs_f32`].
796-
///
797-
/// [`from_secs_f32`]: Duration::from_secs_f32
798-
///
799-
/// This constructor will return an `Err` if `secs` is not finite, negative or overflows `Duration`.
800-
///
801-
/// # Examples
802-
/// ```
803-
/// #![feature(duration_checked_float)]
804-
/// use std::time::Duration;
805-
///
806-
/// let dur = Duration::try_from_secs_f32(2.7);
807-
/// assert_eq!(dur, Ok(Duration::new(2, 700_000_000)));
808-
///
809-
/// let negative = Duration::try_from_secs_f32(-5.0);
810-
/// assert!(negative.is_err());
811-
/// ```
812-
#[unstable(feature = "duration_checked_float", issue = "83400")]
813-
#[inline]
814-
pub const fn try_from_secs_f32(secs: f32) -> Result<Duration, FromSecsError> {
815-
const MAX_NANOS_F32: f32 = ((u64::MAX as u128 + 1) * (NANOS_PER_SEC as u128)) as f32;
816-
let nanos = secs * (NANOS_PER_SEC as f32);
817-
if !nanos.is_finite() {
818-
Err(FromSecsError { kind: FromSecsErrorKind::NonFinite })
819-
} else if nanos >= MAX_NANOS_F32 {
820-
Err(FromSecsError { kind: FromSecsErrorKind::Overflow })
821-
} else if nanos < 0.0 {
822-
Err(FromSecsError { kind: FromSecsErrorKind::Negative })
823-
} else {
824-
let nanos = nanos as u128;
825-
Ok(Duration {
826-
secs: (nanos / (NANOS_PER_SEC as u128)) as u64,
827-
nanos: (nanos % (NANOS_PER_SEC as u128)) as u32,
828-
})
829-
}
830-
}
831-
832786
/// Multiplies `Duration` by `f64`.
833787
///
834788
/// # Panics
835-
/// This method will panic if result is not finite, negative or overflows `Duration`.
789+
/// This method will panic if result is negative, overflows `Duration` or not finite.
836790
///
837791
/// # Examples
838792
/// ```
@@ -854,17 +808,15 @@ impl Duration {
854808
/// Multiplies `Duration` by `f32`.
855809
///
856810
/// # Panics
857-
/// This method will panic if result is not finite, negative or overflows `Duration`.
811+
/// This method will panic if result is negative, overflows `Duration` or not finite.
858812
///
859813
/// # Examples
860814
/// ```
861815
/// use std::time::Duration;
862816
///
863817
/// let dur = Duration::new(2, 700_000_000);
864-
/// // note that due to rounding errors result is slightly different
865-
/// // from 8.478 and 847800.0
866818
/// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_640));
867-
/// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847799, 969_120_256));
819+
/// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847800, 0));
868820
/// ```
869821
#[stable(feature = "duration_float", since = "1.38.0")]
870822
#[must_use = "this returns the result of the operation, \
@@ -878,7 +830,7 @@ impl Duration {
878830
/// Divide `Duration` by `f64`.
879831
///
880832
/// # Panics
881-
/// This method will panic if result is not finite, negative or overflows `Duration`.
833+
/// This method will panic if result is negative, overflows `Duration` or not finite.
882834
///
883835
/// # Examples
884836
/// ```
@@ -901,7 +853,7 @@ impl Duration {
901853
/// Divide `Duration` by `f32`.
902854
///
903855
/// # Panics
904-
/// This method will panic if result is not finite, negative or overflows `Duration`.
856+
/// This method will panic if result is negative, overflows `Duration` or not finite.
905857
///
906858
/// # Examples
907859
/// ```
@@ -910,7 +862,7 @@ impl Duration {
910862
/// let dur = Duration::new(2, 700_000_000);
911863
/// // note that due to rounding errors result is slightly
912864
/// // different from 0.859_872_611
913-
/// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_576));
865+
/// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_579));
914866
/// // note that truncation is used, not rounding
915867
/// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_598));
916868
/// ```
@@ -1267,33 +1219,180 @@ impl fmt::Debug for Duration {
12671219
/// ```
12681220
#[derive(Debug, Clone, PartialEq, Eq)]
12691221
#[unstable(feature = "duration_checked_float", issue = "83400")]
1270-
pub struct FromSecsError {
1271-
kind: FromSecsErrorKind,
1222+
pub struct FromFloatSecsError {
1223+
kind: FromFloatSecsErrorKind,
12721224
}
12731225

1274-
impl FromSecsError {
1226+
impl FromFloatSecsError {
12751227
const fn description(&self) -> &'static str {
12761228
match self.kind {
1277-
FromSecsErrorKind::NonFinite => "non-finite value when converting float to duration",
1278-
FromSecsErrorKind::Overflow => "overflow when converting float to duration",
1279-
FromSecsErrorKind::Negative => "negative value when converting float to duration",
1229+
FromFloatSecsErrorKind::Negative => {
1230+
"can not convert float seconds to Duration: value is negative"
1231+
}
1232+
FromFloatSecsErrorKind::OverflowOrNan => {
1233+
"can not convert float seconds to Duration: value is either too big or NaN"
1234+
}
12801235
}
12811236
}
12821237
}
12831238

12841239
#[unstable(feature = "duration_checked_float", issue = "83400")]
1285-
impl fmt::Display for FromSecsError {
1240+
impl fmt::Display for FromFloatSecsError {
12861241
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1287-
fmt::Display::fmt(self.description(), f)
1242+
self.description().fmt(f)
12881243
}
12891244
}
12901245

12911246
#[derive(Debug, Clone, PartialEq, Eq)]
1292-
enum FromSecsErrorKind {
1293-
// Value is not a finite value (either + or - infinity or NaN).
1294-
NonFinite,
1295-
// Value is too large to store in a `Duration`.
1296-
Overflow,
1247+
enum FromFloatSecsErrorKind {
12971248
// Value is negative.
12981249
Negative,
1250+
// Value is either too big to be represented as `Duration` or `NaN`.
1251+
OverflowOrNan,
1252+
}
1253+
1254+
macro_rules! try_from_secs {
1255+
(
1256+
secs = $secs: expr,
1257+
mantissa_bits = $mant_bits: literal,
1258+
exponent_bits = $exp_bits: literal,
1259+
offset = $offset: literal,
1260+
bits_ty = $bits_ty:ty,
1261+
double_ty = $double_ty:ty,
1262+
) => {{
1263+
const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2;
1264+
const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1;
1265+
const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1;
1266+
1267+
if $secs.is_sign_negative() {
1268+
return Err(FromFloatSecsError { kind: FromFloatSecsErrorKind::Negative });
1269+
}
1270+
1271+
let bits = $secs.to_bits();
1272+
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
1273+
let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP;
1274+
1275+
let (secs, nanos) = if exp < -30 {
1276+
// the input represents less than 1ns.
1277+
(0u64, 0u32)
1278+
} else if exp < 0 {
1279+
// the input is less than 1 second
1280+
let t = <$double_ty>::from(mant) << ($offset + exp);
1281+
let nanos = (u128::from(NANOS_PER_SEC) * u128::from(t)) >> ($mant_bits + $offset);
1282+
(0, nanos as u32)
1283+
} else if exp < $mant_bits {
1284+
let secs = mant >> ($mant_bits - exp);
1285+
let t = <$double_ty>::from((mant << exp) & MANT_MASK);
1286+
let nanos = (<$double_ty>::from(NANOS_PER_SEC) * t) >> $mant_bits;
1287+
(u64::from(secs), nanos as u32)
1288+
} else if exp < 64 {
1289+
// the input has no fractional part
1290+
let secs = u64::from(mant) << (exp - $mant_bits);
1291+
(secs, 0)
1292+
} else {
1293+
return Err(FromFloatSecsError { kind: FromFloatSecsErrorKind::OverflowOrNan });
1294+
};
1295+
1296+
Ok(Duration { secs, nanos })
1297+
}};
1298+
}
1299+
1300+
impl Duration {
1301+
/// The checked version of [`from_secs_f32`].
1302+
///
1303+
/// [`from_secs_f32`]: Duration::from_secs_f32
1304+
///
1305+
/// This constructor will return an `Err` if `secs` is negative, overflows `Duration` or not finite.
1306+
///
1307+
/// # Examples
1308+
/// ```
1309+
/// #![feature(duration_checked_float)]
1310+
///
1311+
/// use std::time::Duration;
1312+
///
1313+
/// let res = Duration::try_from_secs_f32(0.0);
1314+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1315+
/// let res = Duration::try_from_secs_f32(1e-20);
1316+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1317+
/// let res = Duration::try_from_secs_f32(4.2e-7);
1318+
/// assert_eq!(res, Ok(Duration::new(0, 419)));
1319+
/// let res = Duration::try_from_secs_f32(2.7);
1320+
/// assert_eq!(res, Ok(Duration::new(2, 700_000_047)));
1321+
/// let res = Duration::try_from_secs_f32(3e10);
1322+
/// assert_eq!(res, Ok(Duration::new(30_000_001_024, 0)));
1323+
/// // subnormal float:
1324+
/// let res = Duration::try_from_secs_f32(f32::from_bits(1));
1325+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1326+
/// // conversion uses truncation, not rounding
1327+
/// let res = Duration::try_from_secs_f32(0.999e-9);
1328+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1329+
///
1330+
/// let res = Duration::try_from_secs_f32(-5.0);
1331+
/// assert!(res.is_err());
1332+
/// let res = Duration::try_from_secs_f32(f32::NAN);
1333+
/// assert!(res.is_err());
1334+
/// let res = Duration::try_from_secs_f32(2e19);
1335+
/// assert!(res.is_err());
1336+
/// ```
1337+
#[unstable(feature = "duration_checked_float", issue = "83400")]
1338+
#[inline]
1339+
pub const fn try_from_secs_f32(secs: f32) -> Result<Duration, FromFloatSecsError> {
1340+
try_from_secs!(
1341+
secs = secs,
1342+
mantissa_bits = 23,
1343+
exponent_bits = 8,
1344+
offset = 41,
1345+
bits_ty = u32,
1346+
double_ty = u64,
1347+
)
1348+
}
1349+
1350+
/// The checked version of [`from_secs_f64`].
1351+
///
1352+
/// [`from_secs_f64`]: Duration::from_secs_f64
1353+
///
1354+
/// This constructor will return an `Err` if `secs` is negative, overflows `Duration` or not finite.
1355+
///
1356+
/// # Examples
1357+
/// ```
1358+
/// #![feature(duration_checked_float)]
1359+
///
1360+
/// use std::time::Duration;
1361+
///
1362+
/// let res = Duration::try_from_secs_f64(0.0);
1363+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1364+
/// let res = Duration::try_from_secs_f64(1e-20);
1365+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1366+
/// let res = Duration::try_from_secs_f64(4.2e-7);
1367+
/// assert_eq!(res, Ok(Duration::new(0, 420)));
1368+
/// let res = Duration::try_from_secs_f64(2.7);
1369+
/// assert_eq!(res, Ok(Duration::new(2, 700_000_000)));
1370+
/// let res = Duration::try_from_secs_f64(3e10);
1371+
/// assert_eq!(res, Ok(Duration::new(30_000_000_000, 0)));
1372+
/// // subnormal float
1373+
/// let res = Duration::try_from_secs_f64(f64::from_bits(1));
1374+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1375+
/// // conversion uses truncation, not rounding
1376+
/// let res = Duration::try_from_secs_f32(0.999e-9);
1377+
/// assert_eq!(res, Ok(Duration::new(0, 0)));
1378+
///
1379+
/// let res = Duration::try_from_secs_f64(-5.0);
1380+
/// assert!(res.is_err());
1381+
/// let res = Duration::try_from_secs_f64(f64::NAN);
1382+
/// assert!(res.is_err());
1383+
/// let res = Duration::try_from_secs_f64(2e19);
1384+
/// assert!(res.is_err());
1385+
/// ```
1386+
#[unstable(feature = "duration_checked_float", issue = "83400")]
1387+
#[inline]
1388+
pub const fn try_from_secs_f64(secs: f64) -> Result<Duration, FromFloatSecsError> {
1389+
try_from_secs!(
1390+
secs = secs,
1391+
mantissa_bits = 52,
1392+
exponent_bits = 11,
1393+
offset = 44,
1394+
bits_ty = u64,
1395+
double_ty = u128,
1396+
)
1397+
}
12991398
}

0 commit comments

Comments
 (0)