Skip to content

Commit faf2b7f

Browse files
authored
Rollup merge of #90247 - newpavlov:duration_float_fract, r=nagisa
Improve Duration::try_from_secs_f32/64 accuracy by directly processing exponent and mantissa Closes: #90225 The methods now implement direct processing of exponent and mantissa, which should result in the best possible conversion accuracy (modulo truncation, i.e. float value of 19.995 ns will be represented as 19 ns).
2 parents a7f3757 + e0bcf77 commit faf2b7f

File tree

3 files changed

+202
-103
lines changed

3 files changed

+202
-103
lines changed

library/core/src/time.rs

+200-101
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)