]> git.lizzy.rs Git - rust.git/commitdiff
faster parsing when not possible to overflow
authorGiles Cope <gilescope@gmail.com>
Mon, 10 May 2021 20:56:27 +0000 (21:56 +0100)
committergilescope <gilescope@gmail.com>
Sat, 26 Mar 2022 14:25:18 +0000 (14:25 +0000)
library/core/src/num/mod.rs

index dca8ffa4e2c89388cc6e72f340512f1b6d648839..5fd697fd4285f40e2f9b53ee6bc7cd496fdaf58b 100644 (file)
@@ -970,12 +970,14 @@ pub enum FpCategory {
 
 #[doc(hidden)]
 trait FromStrRadixHelper: PartialOrd + Copy {
-    fn min_value() -> Self;
-    fn max_value() -> Self;
+    const MIN: Self;
     fn from_u32(u: u32) -> Self;
     fn checked_mul(&self, other: u32) -> Option<Self>;
     fn checked_sub(&self, other: u32) -> Option<Self>;
     fn checked_add(&self, other: u32) -> Option<Self>;
+    unsafe fn unchecked_mul(&self, other: u32) -> Self;
+    unsafe fn unchecked_sub(&self, other: u32) -> Self;
+    unsafe fn unchecked_add(&self, other: u32) -> Self;
 }
 
 macro_rules! from_str_radix_int_impl {
@@ -993,10 +995,7 @@ fn from_str(src: &str) -> Result<Self, ParseIntError> {
 
 macro_rules! doit {
     ($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
-        #[inline]
-        fn min_value() -> Self { Self::MIN }
-        #[inline]
-        fn max_value() -> Self { Self::MAX }
+        const MIN: Self = Self::MIN;
         #[inline]
         fn from_u32(u: u32) -> Self { u as Self }
         #[inline]
@@ -1011,6 +1010,27 @@ fn checked_sub(&self, other: u32) -> Option<Self> {
         fn checked_add(&self, other: u32) -> Option<Self> {
             Self::checked_add(*self, other as Self)
         }
+        #[inline]
+        unsafe fn unchecked_mul(&self, other: u32) -> Self {
+            // SAFETY:  Conditions of `Self::unchecked_mul` must be upheld by the caller.
+            unsafe {
+                Self::unchecked_mul(*self, other as Self)
+            }
+        }
+        #[inline]
+        unsafe fn unchecked_sub(&self, other: u32) -> Self {
+            // SAFETY:  Conditions of `Self::unchecked_sub` must be upheld by the caller.
+            unsafe {
+                Self::unchecked_sub(*self, other as Self)
+            }
+        }
+        #[inline]
+        unsafe fn unchecked_add(&self, other: u32) -> Self {
+            // SAFETY: Conditions of `Self::unchecked_add` must be upheld by the caller.
+            unsafe {
+                Self::unchecked_add(*self, other as Self)
+            }
+        }
     })*)
 }
 doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
@@ -1029,7 +1049,7 @@ fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, Par
         return Err(PIE { kind: Empty });
     }
 
-    let is_signed_ty = T::from_u32(0) > T::min_value();
+    let is_signed_ty = T::from_u32(0) > T::MIN;
 
     // all valid digits are ascii, so we will just iterate over the utf8 bytes
     // and cast them to chars. .to_digit() will safely return None for anything
@@ -1047,37 +1067,32 @@ fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, Par
     };
 
     let mut result = T::from_u32(0);
-    if is_positive {
-        // The number is positive
-        for &c in digits {
-            let x = match (c as char).to_digit(radix) {
-                Some(x) => x,
-                None => return Err(PIE { kind: InvalidDigit }),
-            };
-            result = match result.checked_mul(radix) {
-                Some(result) => result,
-                None => return Err(PIE { kind: PosOverflow }),
-            };
-            result = match result.checked_add(x) {
-                Some(result) => result,
-                None => return Err(PIE { kind: PosOverflow }),
-            };
+
+    if radix <= 16 && digits.len() <= mem::size_of::<T>() * 2 - is_signed_ty as usize {
+        // SAFETY: Consider the highest radix of 16:
+        // `u8::MAX` is `ff` (2 characters), `u16::MAX` is `ffff` (4 characters)
+        // We can be sure that any src len of 2 would fit in a u8 so we don't need
+        // to check for overflow.
+        unsafe {
+            let unchecked_additive_op =
+                if is_positive { T::unchecked_add } else { T::unchecked_sub };
+
+            for &c in digits {
+                result = result.unchecked_mul(radix);
+                let x = (c as char).to_digit(radix).ok_or(PIE { kind: InvalidDigit })?;
+                result = unchecked_additive_op(&result, x);
+            }
         }
     } else {
-        // The number is negative
+        let additive_op = if is_positive { T::checked_add } else { T::checked_sub };
+        let overflow_err = || PIE { kind: if is_positive { PosOverflow } else { NegOverflow } };
+
         for &c in digits {
-            let x = match (c as char).to_digit(radix) {
-                Some(x) => x,
-                None => return Err(PIE { kind: InvalidDigit }),
-            };
-            result = match result.checked_mul(radix) {
-                Some(result) => result,
-                None => return Err(PIE { kind: NegOverflow }),
-            };
-            result = match result.checked_sub(x) {
-                Some(result) => result,
-                None => return Err(PIE { kind: NegOverflow }),
-            };
+            let mul = result.checked_mul(radix);
+            let x = (c as char).to_digit(radix).ok_or(PIE { kind: InvalidDigit })?;
+            // multiply done early for performance reasons.
+            result = mul.ok_or_else(overflow_err)?;
+            result = additive_op(&result, x).ok_or_else(overflow_err)?;
         }
     }
     Ok(result)