about summary refs log tree commit diff
diff options
context:
space:
mode:
authorАртём Павлов [Artyom Pavlov] <newpavlov@gmail.com>2022-05-25 05:01:11 +0300
committerАртём Павлов [Artyom Pavlov] <newpavlov@gmail.com>2022-05-25 05:01:11 +0300
commitc2d8445cde2b515d54891964efcdc43722c5d5ca (patch)
treeaffa855a58da630cf3c5d06415270f3307415478
parent65c75b2db7827f8faa63c332139a3264b4ab9427 (diff)
downloadrust-c2d8445cde2b515d54891964efcdc43722c5d5ca.tar.gz
rust-c2d8445cde2b515d54891964efcdc43722c5d5ca.zip
implement tie to even
-rw-r--r--library/core/src/time.rs92
1 files changed, 73 insertions, 19 deletions
diff --git a/library/core/src/time.rs b/library/core/src/time.rs
index fc2484f0cdd..7f23cb83e09 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]