From 65c75b2db7827f8faa63c332139a3264b4ab9427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Thu, 14 Apr 2022 20:44:09 +0300 Subject: [PATCH 1/6] Use rounding in float to Duration conversion methods --- library/core/src/time.rs | 61 +++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 243c044b5d9d0..fc2484f0cdd44 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -730,9 +730,9 @@ impl Duration { /// // subnormal float /// let res = Duration::from_secs_f64(f64::from_bits(1)); /// assert_eq!(res, Duration::new(0, 0)); - /// // conversion uses truncation, not rounding + /// // conversion uses rounding /// let res = Duration::from_secs_f64(0.999e-9); - /// assert_eq!(res, Duration::new(0, 0)); + /// assert_eq!(res, Duration::new(0, 1)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use] @@ -760,17 +760,17 @@ impl Duration { /// let res = Duration::from_secs_f32(1e-20); /// assert_eq!(res, Duration::new(0, 0)); /// let res = Duration::from_secs_f32(4.2e-7); - /// assert_eq!(res, Duration::new(0, 419)); + /// assert_eq!(res, Duration::new(0, 420)); /// let res = Duration::from_secs_f32(2.7); - /// assert_eq!(res, Duration::new(2, 700_000_047)); + /// assert_eq!(res, Duration::new(2, 700_000_048)); /// let res = Duration::from_secs_f32(3e10); /// assert_eq!(res, Duration::new(30_000_001_024, 0)); /// // subnormal float /// let res = Duration::from_secs_f32(f32::from_bits(1)); /// assert_eq!(res, Duration::new(0, 0)); - /// // conversion uses truncation, not rounding + /// // conversion uses rounding /// let res = Duration::from_secs_f32(0.999e-9); - /// assert_eq!(res, Duration::new(0, 0)); + /// assert_eq!(res, Duration::new(0, 1)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use] @@ -815,7 +815,7 @@ impl Duration { /// use std::time::Duration; /// /// let dur = Duration::new(2, 700_000_000); - /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_640)); + /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_641)); /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847800, 0)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] @@ -838,8 +838,7 @@ impl Duration { /// /// let dur = Duration::new(2, 700_000_000); /// assert_eq!(dur.div_f64(3.14), Duration::new(0, 859_872_611)); - /// // note that truncation is used, not rounding - /// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_598)); + /// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_599)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use = "this returns the result of the operation, \ @@ -862,9 +861,8 @@ impl Duration { /// let dur = Duration::new(2, 700_000_000); /// // note that due to rounding errors result is slightly /// // different from 0.859_872_611 - /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_579)); - /// // note that truncation is used, not rounding - /// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_598)); + /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_580)); + /// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_599)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use = "this returns the result of the operation, \ @@ -1278,13 +1276,30 @@ macro_rules! try_from_secs { } else if exp < 0 { // the input is less than 1 second let t = <$double_ty>::from(mant) << ($offset + exp); - let nanos = (u128::from(NANOS_PER_SEC) * u128::from(t)) >> ($mant_bits + $offset); - (0, nanos as u32) + let nanos_offset = $mant_bits + $offset; + let nanos_tmp = u128::from(NANOS_PER_SEC) * u128::from(t); + let nanos = (nanos_tmp >> nanos_offset) as u32; + if nanos_tmp & (1 << (nanos_offset - 1)) == 0 { + (0, nanos) + } else if nanos + 1 != NANOS_PER_SEC { + (0, nanos + 1) + } else { + (1, 0) + } } else if exp < $mant_bits { - let secs = mant >> ($mant_bits - exp); + let secs = u64::from(mant >> ($mant_bits - exp)); let t = <$double_ty>::from((mant << exp) & MANT_MASK); - let nanos = (<$double_ty>::from(NANOS_PER_SEC) * t) >> $mant_bits; - (u64::from(secs), nanos as u32) + let nanos_tmp = <$double_ty>::from(NANOS_PER_SEC) * t; + let nanos = (nanos_tmp >> $mant_bits) as u32; + if nanos_tmp & (1 << ($mant_bits - 1)) == 0 { + (secs, nanos) + } else if nanos + 1 != NANOS_PER_SEC { + (secs, nanos + 1) + } else { + // `secs + 1` can not overflow since `exp` is less than `$mant_bits` + // and the latter is less than 64 bits for both `f32` and `f64` + (secs + 1, 0) + } } else if exp < 64 { // the input has no fractional part let secs = u64::from(mant) << (exp - $mant_bits); @@ -1315,17 +1330,17 @@ impl Duration { /// let res = Duration::try_from_secs_f32(1e-20); /// assert_eq!(res, Ok(Duration::new(0, 0))); /// let res = Duration::try_from_secs_f32(4.2e-7); - /// assert_eq!(res, Ok(Duration::new(0, 419))); + /// assert_eq!(res, Ok(Duration::new(0, 420))); /// let res = Duration::try_from_secs_f32(2.7); - /// assert_eq!(res, Ok(Duration::new(2, 700_000_047))); + /// assert_eq!(res, Ok(Duration::new(2, 700_000_048))); /// let res = Duration::try_from_secs_f32(3e10); /// assert_eq!(res, Ok(Duration::new(30_000_001_024, 0))); /// // subnormal float: /// let res = Duration::try_from_secs_f32(f32::from_bits(1)); /// assert_eq!(res, Ok(Duration::new(0, 0))); - /// // conversion uses truncation, not rounding + /// // conversion uses rounding /// let res = Duration::try_from_secs_f32(0.999e-9); - /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// assert_eq!(res, Ok(Duration::new(0, 1))); /// /// let res = Duration::try_from_secs_f32(-5.0); /// assert!(res.is_err()); @@ -1372,9 +1387,9 @@ impl Duration { /// // subnormal float /// let res = Duration::try_from_secs_f64(f64::from_bits(1)); /// assert_eq!(res, Ok(Duration::new(0, 0))); - /// // conversion uses truncation, not rounding + /// // conversion uses rounding /// let res = Duration::try_from_secs_f32(0.999e-9); - /// assert_eq!(res, Ok(Duration::new(0, 0))); + /// assert_eq!(res, Ok(Duration::new(0, 1))); /// /// let res = Duration::try_from_secs_f64(-5.0); /// assert!(res.is_err()); From c2d8445cde2b515d54891964efcdc43722c5d5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 25 May 2022 05:01:11 +0300 Subject: [PATCH 2/6] implement tie to even --- library/core/src/time.rs | 92 +++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index fc2484f0cdd44..7f23cb83e0999 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1270,8 +1270,8 @@ macro_rules! try_from_secs { let mant = (bits & MANT_MASK) | (MANT_MASK + 1); let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP; - let (secs, nanos) = if exp < -30 { - // the input represents less than 1ns. + let (secs, nanos) = if exp < -31 { + // the input represents less than 1ns and can not be rounded to it (0u64, 0u32) } else if exp < 0 { // the input is less than 1 second @@ -1279,27 +1279,37 @@ macro_rules! try_from_secs { let nanos_offset = $mant_bits + $offset; let nanos_tmp = u128::from(NANOS_PER_SEC) * u128::from(t); let nanos = (nanos_tmp >> nanos_offset) as u32; - if nanos_tmp & (1 << (nanos_offset - 1)) == 0 { - (0, nanos) - } else if nanos + 1 != NANOS_PER_SEC { - (0, nanos + 1) - } else { - (1, 0) - } + + let rem_mask = (1 << nanos_offset) - 1; + let rem_msb_mask = 1 << (nanos_offset - 1); + let rem = nanos_tmp & rem_mask; + let is_tie = rem == rem_msb_mask; + let is_even = (nanos & 1) == 0; + let rem_msb = nanos_tmp & rem_msb_mask == 0; + let add_ns = !(rem_msb || (is_even && is_tie)); + + // note that neither `f32`, nor `f64` can represent + // 0.999_999_999_5 exactly, so the nanos part + // never will be equal to 10^9. + (0, nanos + add_ns as u32) } else if exp < $mant_bits { let secs = u64::from(mant >> ($mant_bits - exp)); let t = <$double_ty>::from((mant << exp) & MANT_MASK); + let nanos_offset = $mant_bits; let nanos_tmp = <$double_ty>::from(NANOS_PER_SEC) * t; - let nanos = (nanos_tmp >> $mant_bits) as u32; - if nanos_tmp & (1 << ($mant_bits - 1)) == 0 { - (secs, nanos) - } else if nanos + 1 != NANOS_PER_SEC { - (secs, nanos + 1) - } else { - // `secs + 1` can not overflow since `exp` is less than `$mant_bits` - // and the latter is less than 64 bits for both `f32` and `f64` - (secs + 1, 0) - } + let nanos = (nanos_tmp >> nanos_offset) as u32; + + let rem_mask = (1 << nanos_offset) - 1; + let rem_msb_mask = 1 << (nanos_offset - 1); + let rem = nanos_tmp & rem_mask; + let is_tie = rem == rem_msb_mask; + let is_even = (nanos & 1) == 0; + let rem_msb = nanos_tmp & rem_msb_mask == 0; + let add_ns = !(rem_msb || (is_even && is_tie)); + + // neither `f32`, nor `f64` can represent x.999_999_999_5 exactly, + // so the nanos part never will be equal to 10^9 + (secs, nanos + add_ns as u32) } else if exp < 64 { // the input has no fractional part let secs = u64::from(mant) << (exp - $mant_bits); @@ -1348,6 +1358,28 @@ impl Duration { /// assert!(res.is_err()); /// let res = Duration::try_from_secs_f32(2e19); /// assert!(res.is_err()); + /// + /// // this method uses round to nearest, ties to even + /// + /// // this float represents exactly 976562.5e-9 + /// let val = f32::from_bits(0x3A80_0000); + /// let res = Duration::try_from_secs_f32(val); + /// assert_eq!(res, Ok(Duration::new(0, 976_562))); + /// + /// // this float represents exactly 2929687.5e-9 + /// let val = f32::from_bits(0x3B40_0000); + /// let res = Duration::try_from_secs_f32(val); + /// assert_eq!(res, Ok(Duration::new(0, 2_929_688))); + /// + /// // this float represents exactly 1.000_976_562_5 + /// let val = f32::from_bits(0x3F802000); + /// let res = Duration::try_from_secs_f32(val); + /// assert_eq!(res, Ok(Duration::new(1, 976_562))); + /// + /// // this float represents exactly 1.002_929_687_5 + /// let val = f32::from_bits(0x3F806000); + /// let res = Duration::try_from_secs_f32(val); + /// assert_eq!(res, Ok(Duration::new(1, 2_929_688))); /// ``` #[unstable(feature = "duration_checked_float", issue = "83400")] #[inline] @@ -1397,6 +1429,28 @@ impl Duration { /// assert!(res.is_err()); /// let res = Duration::try_from_secs_f64(2e19); /// assert!(res.is_err()); + /// + /// // this method uses round to nearest, ties to even + /// + /// // this float represents exactly 976562.5e-9 + /// let val = f64::from_bits(0x3F50_0000_0000_0000); + /// let res = Duration::try_from_secs_f64(val); + /// assert_eq!(res, Ok(Duration::new(0, 976_562))); + /// + /// // this float represents exactly 2929687.5e-9 + /// let val = f64::from_bits(0x3F68_0000_0000_0000); + /// let res = Duration::try_from_secs_f64(val); + /// assert_eq!(res, Ok(Duration::new(0, 2_929_688))); + /// + /// // this float represents exactly 1.000_976_562_5 + /// let val = f64::from_bits(0x3FF0_0400_0000_0000); + /// let res = Duration::try_from_secs_f64(val); + /// assert_eq!(res, Ok(Duration::new(1, 976_562))); + /// + /// // this float represents exactly 1.002_929_687_5 + /// let val = f64::from_bits(0x3_FF00_C000_0000_000); + /// let res = Duration::try_from_secs_f64(val); + /// assert_eq!(res, Ok(Duration::new(1, 2_929_688))); /// ``` #[unstable(feature = "duration_checked_float", issue = "83400")] #[inline] From 26f859463eb756e7363cc32f5b5ae795790803b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Wed, 25 May 2022 05:14:30 +0300 Subject: [PATCH 3/6] tweak doctests --- library/core/src/time.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 7f23cb83e0999..6d098acb5ea51 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1348,9 +1348,6 @@ impl Duration { /// // subnormal float: /// let res = Duration::try_from_secs_f32(f32::from_bits(1)); /// assert_eq!(res, Ok(Duration::new(0, 0))); - /// // conversion uses rounding - /// let res = Duration::try_from_secs_f32(0.999e-9); - /// assert_eq!(res, Ok(Duration::new(0, 1))); /// /// let res = Duration::try_from_secs_f32(-5.0); /// assert!(res.is_err()); @@ -1359,7 +1356,9 @@ impl Duration { /// let res = Duration::try_from_secs_f32(2e19); /// assert!(res.is_err()); /// - /// // this method uses round to nearest, ties to even + /// // the conversion uses rounding with tie resolution to even + /// let res = Duration::try_from_secs_f32(0.999e-9); + /// assert_eq!(res, Ok(Duration::new(0, 1))); /// /// // this float represents exactly 976562.5e-9 /// let val = f32::from_bits(0x3A80_0000); @@ -1419,9 +1418,6 @@ impl Duration { /// // subnormal float /// let res = Duration::try_from_secs_f64(f64::from_bits(1)); /// assert_eq!(res, Ok(Duration::new(0, 0))); - /// // conversion uses rounding - /// let res = Duration::try_from_secs_f32(0.999e-9); - /// assert_eq!(res, Ok(Duration::new(0, 1))); /// /// let res = Duration::try_from_secs_f64(-5.0); /// assert!(res.is_err()); @@ -1430,7 +1426,9 @@ impl Duration { /// let res = Duration::try_from_secs_f64(2e19); /// assert!(res.is_err()); /// - /// // this method uses round to nearest, ties to even + /// // the conversion uses rounding with tie resolution to even + /// let res = Duration::try_from_secs_f64(0.999e-9); + /// assert_eq!(res, Ok(Duration::new(0, 1))); /// /// // this float represents exactly 976562.5e-9 /// let val = f64::from_bits(0x3F50_0000_0000_0000); From 06af3a63a597a40604976666dea19a503557855a Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Fri, 27 May 2022 00:22:56 +0000 Subject: [PATCH 4/6] add debug asserts --- library/core/src/time.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 6d098acb5ea51..46fab01b1bf20 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1290,8 +1290,10 @@ macro_rules! try_from_secs { // note that neither `f32`, nor `f64` can represent // 0.999_999_999_5 exactly, so the nanos part - // never will be equal to 10^9. - (0, nanos + add_ns as u32) + // never will be equal to NANOS_PER_SEC + let nanos = nanos + add_ns as u32; + debug_assert!(nanos < NANOS_PER_SEC); + (0, nanos) } else if exp < $mant_bits { let secs = u64::from(mant >> ($mant_bits - exp)); let t = <$double_ty>::from((mant << exp) & MANT_MASK); @@ -1308,8 +1310,10 @@ macro_rules! try_from_secs { let add_ns = !(rem_msb || (is_even && is_tie)); // neither `f32`, nor `f64` can represent x.999_999_999_5 exactly, - // so the nanos part never will be equal to 10^9 - (secs, nanos + add_ns as u32) + // so the nanos part never will be equal to NANOS_PER_SEC + let nanos = nanos + add_ns as u32; + debug_assert!(nanos < NANOS_PER_SEC); + (secs, nanos) } else if exp < 64 { // the input has no fractional part let secs = u64::from(mant) << (exp - $mant_bits); From 38609cd8a9113969746e6a0598fca7d13788e8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 27 May 2022 04:59:01 +0300 Subject: [PATCH 5/6] fix nanos overflow for f64 --- library/core/src/time.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 46fab01b1bf20..6983f10df3f7f 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1288,12 +1288,14 @@ macro_rules! try_from_secs { let rem_msb = nanos_tmp & rem_msb_mask == 0; let add_ns = !(rem_msb || (is_even && is_tie)); - // note that neither `f32`, nor `f64` can represent - // 0.999_999_999_5 exactly, so the nanos part - // never will be equal to NANOS_PER_SEC + // f32 does not have enough presicion to trigger the second branch + // since it can not represent numbers between 0.999_999_940_395 and 1.0. let nanos = nanos + add_ns as u32; - debug_assert!(nanos < NANOS_PER_SEC); - (0, nanos) + if ($mant_bits == 23) || (nanos != NANOS_PER_SEC) { + (0, nanos) + } else { + (1, 0) + } } else if exp < $mant_bits { let secs = u64::from(mant >> ($mant_bits - exp)); let t = <$double_ty>::from((mant << exp) & MANT_MASK); @@ -1309,11 +1311,16 @@ macro_rules! try_from_secs { let rem_msb = nanos_tmp & rem_msb_mask == 0; let add_ns = !(rem_msb || (is_even && is_tie)); - // neither `f32`, nor `f64` can represent x.999_999_999_5 exactly, - // so the nanos part never will be equal to NANOS_PER_SEC + // f32 does not have enough presicion to trigger the second branch. + // For example, it can not represent numbers between 1.999_999_880... + // and 2.0. Bigger values result in even smaller presicion of the + // fractional part. let nanos = nanos + add_ns as u32; - debug_assert!(nanos < NANOS_PER_SEC); - (secs, nanos) + if ($mant_bits == 23) || (nanos != NANOS_PER_SEC) { + (secs, nanos) + } else { + (secs + 1, 0) + } } else if exp < 64 { // the input has no fractional part let secs = u64::from(mant) << (exp - $mant_bits); @@ -1433,6 +1440,14 @@ impl Duration { /// // the conversion uses rounding with tie resolution to even /// let res = Duration::try_from_secs_f64(0.999e-9); /// assert_eq!(res, Ok(Duration::new(0, 1))); + /// let res = Duration::try_from_secs_f64(0.999_999_999_499); + /// assert_eq!(res, Ok(Duration::new(0, 999_999_999))); + /// let res = Duration::try_from_secs_f64(0.999_999_999_501); + /// assert_eq!(res, Ok(Duration::new(1, 0))); + /// let res = Duration::try_from_secs_f64(42.999_999_999_499); + /// assert_eq!(res, Ok(Duration::new(42, 999_999_999))); + /// let res = Duration::try_from_secs_f64(42.999_999_999_501); + /// assert_eq!(res, Ok(Duration::new(43, 0))); /// /// // this float represents exactly 976562.5e-9 /// let val = f64::from_bits(0x3F50_0000_0000_0000); From 6495963d5a2d3e36ce799cfb932aa1a4b080f262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Fri, 27 May 2022 05:15:22 +0300 Subject: [PATCH 6/6] fmt --- library/core/src/time.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 6983f10df3f7f..d653f715f6d47 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1291,11 +1291,7 @@ macro_rules! try_from_secs { // f32 does not have enough presicion to trigger the second branch // since it can not represent numbers between 0.999_999_940_395 and 1.0. let nanos = nanos + add_ns as u32; - if ($mant_bits == 23) || (nanos != NANOS_PER_SEC) { - (0, nanos) - } else { - (1, 0) - } + if ($mant_bits == 23) || (nanos != NANOS_PER_SEC) { (0, nanos) } else { (1, 0) } } else if exp < $mant_bits { let secs = u64::from(mant >> ($mant_bits - exp)); let t = <$double_ty>::from((mant << exp) & MANT_MASK);