diff --git a/benches/benches.rs b/benches/benches.rs index 5d7fc60..7d33081 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -1,4 +1,4 @@ -use atoi::{FromRadix10, FromRadix10Checked, FromRadix16, FromRadix16Checked, FromRadix10Signed}; +use atoi::{FromRadix10, FromRadix10Checked, FromRadix10Signed, FromRadix16, FromRadix16Checked}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::str; diff --git a/src/builtin.rs b/src/builtin.rs index 1cb7bf5..7af5b27 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,71 +1,104 @@ -use crate::{ - FromRadix10, FromRadix10Checked, FromRadix10Signed, FromRadix10SignedChecked, FromRadix16, - FromRadix16Checked, Integer, MaxNumDigits, -}; - -macro_rules! impl_traits_using_integer { - ($t:ident) => { - impl FromRadix10 for $t { - fn from_radix_10(text: &[u8]) -> (Self, usize) { - let (Integer(i), p) = Integer::::from_radix_10(text); - (i, p) - } - } - - impl FromRadix10Signed for $t { - fn from_radix_10_signed(text: &[u8]) -> (Self, usize) { - let (Integer(i), p) = Integer::::from_radix_10_signed(text); - (i, p) - } - } - - impl MaxNumDigits for $t { - fn max_num_digits(radix: Self) -> usize { - Integer::::max_num_digits(Integer(radix)) - } - - fn max_num_digits_negative(radix: Self) -> usize { - Integer::::max_num_digits_negative(Integer(radix)) - } - } - - impl FromRadix10Checked for $t { - fn from_radix_10_checked(text: &[u8]) -> (Option, usize) { - let (o, p) = Integer::::from_radix_10_checked(text); - (o.map(|i| i.0), p) - } - } - - impl FromRadix10SignedChecked for $t { - fn from_radix_10_signed_checked(text: &[u8]) -> (Option, usize) { - let (o, p) = Integer::::from_radix_10_signed_checked(text); - (o.map(|i| i.0), p) - } - } - - impl FromRadix16 for $t { - fn from_radix_16(text: &[u8]) -> (Self, usize) { - let (Integer(i), p) = Integer::::from_radix_16(text); - (i, p) - } - } - - impl FromRadix16Checked for $t { - fn from_radix_16_checked(text: &[u8]) -> (Option, usize) { - let (o, p) = Integer::::from_radix_16_checked(text); - (o.map(|i| i.0), p) - } - } - }; -} - -impl_traits_using_integer!(i8); -impl_traits_using_integer!(u8); -impl_traits_using_integer!(i16); -impl_traits_using_integer!(u16); -impl_traits_using_integer!(i32); -impl_traits_using_integer!(u32); -impl_traits_using_integer!(i64); -impl_traits_using_integer!(u64); -impl_traits_using_integer!(i128); -impl_traits_using_integer!(u128); +//! Implementation for numerical types native to Rust. Currently we use macros instead of one +//! generic implementation relying on traits. This way we can better specialize some implementations +//! in the future without relying on specializations for generic implementation to become stable. +//! A generic implementation for "any integer" can still be invoked using the `Integer` wrapper. + +use crate::{ + ascii_to_digit, FromDigit, FromRadix10, FromRadix10Checked, FromRadix10Signed, + FromRadix10SignedChecked, FromRadix16, FromRadix16Checked, Integer, MaxNumDigits, +}; + +macro_rules! impl_traits_using_integer { + ($t:ident) => { + impl FromRadix10 for $t { + fn from_radix_10(text: &[u8]) -> (Self, usize) { + let mut index = 0; + let mut number = 0; + while index != text.len() { + if let Some(digit) = ascii_to_digit::<$t>(text[index]) { + number *= 10; + number += digit; + index += 1; + } else { + break; + } + } + (number, index) + } + } + + impl FromRadix10Signed for $t { + fn from_radix_10_signed(text: &[u8]) -> (Self, usize) { + let (Integer(i), p) = Integer::::from_radix_10_signed(text); + (i, p) + } + } + + impl MaxNumDigits for $t { + fn max_num_digits(radix: Self) -> usize { + Integer::::max_num_digits(Integer(radix)) + } + + fn max_num_digits_negative(radix: Self) -> usize { + Integer::::max_num_digits_negative(Integer(radix)) + } + } + + impl FromRadix10Checked for $t { + fn from_radix_10_checked(text: &[u8]) -> (Option, usize) { + let (o, p) = Integer::::from_radix_10_checked(text); + (o.map(|i| i.0), p) + } + } + + impl FromRadix10SignedChecked for $t { + fn from_radix_10_signed_checked(text: &[u8]) -> (Option, usize) { + let (o, p) = Integer::::from_radix_10_signed_checked(text); + (o.map(|i| i.0), p) + } + } + + impl FromRadix16 for $t { + fn from_radix_16(text: &[u8]) -> (Self, usize) { + let (Integer(i), p) = Integer::::from_radix_16(text); + (i, p) + } + } + + impl FromRadix16Checked for $t { + fn from_radix_16_checked(text: &[u8]) -> (Option, usize) { + let (o, p) = Integer::::from_radix_16_checked(text); + (o.map(|i| i.0), p) + } + } + + impl FromDigit for $t { + fn from_digit(digit: u8) -> Option { + match digit { + b'0' => Some(0), + b'1' => Some(1), + b'2' => Some(2), + b'3' => Some(3), + b'4' => Some(4), + b'5' => Some(5), + b'6' => Some(6), + b'7' => Some(7), + b'8' => Some(8), + b'9' => Some(9), + _ => None, + } + } + } + }; +} + +impl_traits_using_integer!(i8); +impl_traits_using_integer!(u8); +impl_traits_using_integer!(i16); +impl_traits_using_integer!(u16); +impl_traits_using_integer!(i32); +impl_traits_using_integer!(u32); +impl_traits_using_integer!(i64); +impl_traits_using_integer!(u64); +impl_traits_using_integer!(i128); +impl_traits_using_integer!(u128); diff --git a/src/integer.rs b/src/integer.rs index 820e120..6d8f2a0 100644 --- a/src/integer.rs +++ b/src/integer.rs @@ -1,322 +1,321 @@ -use core::{ops::{AddAssign, MulAssign, SubAssign, DivAssign}, cmp::{max, min}}; - -use num_traits::{Zero, One, CheckedAdd, CheckedSub, CheckedMul, Bounded}; - -use crate::{FromRadix10, FromRadix10Signed, Sign, FromRadix10SignedChecked, MaxNumDigits, FromRadix10Checked, FromRadix16, FromRadix16Checked}; - -/// Wrapper which implements the traits [`crate::FromRadix10`], [`crate::FromRadix10Checked`], -/// [`crate::FromRadix16`] and [`crate::FromRadix16Checked`] for any inductive type. I.e. a type -/// implementing [`One`], [`Zero`], `+=` and `*=`. -pub struct Integer(pub I); - -impl FromRadix10 for Integer -where - I: Zero + One + AddAssign + MulAssign, -{ - fn from_radix_10(text: &[u8]) -> (Self, usize) { - let mut index = 0; - let mut number = I::zero(); - while index != text.len() { - if let Some(digit) = ascii_to_digit(text[index]) { - number *= nth(10); - number += digit; - index += 1; - } else { - break; - } - } - (Integer(number), index) - } -} - -impl FromRadix10Signed for Integer -where - I: Zero + One + AddAssign + SubAssign + MulAssign, -{ - fn from_radix_10_signed(text: &[u8]) -> (Self, usize) { - let mut index; - let mut number = I::zero(); - - let (sign, offset) = text - .first() - .and_then(|&byte| Sign::try_from(byte)) - .map(|sign| (sign, 1)) - .unwrap_or((Sign::Plus, 0)); - - index = offset; - - // Having two dedicated loops for both the negative and the nonnegative case is rather - // verbose, yet performed up to 40% better then a more terse single loop with - // `number += digit * signum`. - - match sign { - Sign::Plus => { - while index != text.len() { - if let Some(digit) = ascii_to_digit::(text[index]) { - number *= nth(10); - number += digit; - index += 1; - } else { - break; - } - } - } - Sign::Minus => { - while index != text.len() { - if let Some(digit) = ascii_to_digit::(text[index]) { - number *= nth(10); - number -= digit; - index += 1; - } else { - break; - } - } - } - } - - (Integer(number), index) - } -} - -/// Converts an ascii character to digit -/// -/// # Example -/// -/// ``` -/// use atoi::ascii_to_digit; -/// assert_eq!(Some(5), ascii_to_digit(b'5')); -/// assert_eq!(None, ascii_to_digit::(b'x')); -/// ``` -pub fn ascii_to_digit(character: u8) -> Option -where - I: Zero + One, -{ - match character { - b'0' => Some(nth(0)), - b'1' => Some(nth(1)), - b'2' => Some(nth(2)), - b'3' => Some(nth(3)), - b'4' => Some(nth(4)), - b'5' => Some(nth(5)), - b'6' => Some(nth(6)), - b'7' => Some(nth(7)), - b'8' => Some(nth(8)), - b'9' => Some(nth(9)), - _ => None, - } -} - -// At least for primitive types this function does not incur runtime costs, since it is only called -// with constants -fn nth(n: u8) -> I -where - I: Zero + One, -{ - let mut i = I::zero(); - for _ in 0..n { - i = i + I::one(); - } - i -} - -impl FromRadix10SignedChecked for Integer -where - I: Zero - + One - + AddAssign - + MulAssign - + SubAssign - + CheckedAdd - + CheckedSub - + CheckedMul - + MaxNumDigits, -{ - fn from_radix_10_signed_checked(text: &[u8]) -> (Option, usize) { - let mut index; - let mut number = I::zero(); - - let (sign, offset) = text - .first() - .and_then(|&byte| Sign::try_from(byte)) - .map(|sign| (sign, 1)) - .unwrap_or((Sign::Plus, 0)); - - index = offset; - - // Having two dedicated loops for both the negative and the nonnegative case is rather - // verbose, yet performed up to 40% better then a more terse single loop with - // `number += digit * signum`. - - match sign { - Sign::Plus => { - let max_safe_digits = max(1, I::max_num_digits(nth(10))) - 1; - let max_safe_index = min(text.len(), max_safe_digits + offset); - while index != max_safe_index { - if let Some(digit) = ascii_to_digit::(text[index]) { - number *= nth(10); - number += digit; - index += 1; - } else { - break; - } - } - // We parsed the digits, which do not need checking now lets see the next one: - let mut number = Some(number); - while index != text.len() { - if let Some(digit) = ascii_to_digit(text[index]) { - number = number.and_then(|n| n.checked_mul(&nth(10))); - number = number.and_then(|n| n.checked_add(&digit)); - index += 1; - } else { - break; - } - } - (number.map(Integer), index) - } - Sign::Minus => { - let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; - let max_safe_index = min(text.len(), max_safe_digits + offset); - while index != max_safe_index { - if let Some(digit) = ascii_to_digit::(text[index]) { - number *= nth(10); - number -= digit; - index += 1; - } else { - break; - } - } - // We parsed the digits, which do not need checking now lets see the next one: - let mut number = Some(number); - while index != text.len() { - if let Some(digit) = ascii_to_digit(text[index]) { - number = number.and_then(|n| n.checked_mul(&nth(10))); - number = number.and_then(|n| n.checked_sub(&digit)); - index += 1; - } else { - break; - } - } - (number.map(Integer), index) - } - } - } -} - -impl FromRadix10Checked for Integer -where - I: Zero + One + FromRadix10 + CheckedMul + CheckedAdd + MaxNumDigits, -{ - fn from_radix_10_checked(text: &[u8]) -> (Option, usize) { - let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; - let (number, mut index) = I::from_radix_10(&text[..min(text.len(), max_safe_digits)]); - let mut number = Some(number); - // We parsed the digits, which do not need checking now lets see the next one: - while index != text.len() { - if let Some(digit) = ascii_to_digit(text[index]) { - number = number.and_then(|n| n.checked_mul(&nth(10))); - number = number.and_then(|n| n.checked_add(&digit)); - index += 1; - } else { - break; - } - } - (number.map(Integer), index) - } -} - -/// Converts an ascii character to digit -fn ascii_to_hexdigit(character: u8) -> Option -where - I: Zero + One, -{ - match character { - b'0' => Some(nth(0)), - b'1' => Some(nth(1)), - b'2' => Some(nth(2)), - b'3' => Some(nth(3)), - b'4' => Some(nth(4)), - b'5' => Some(nth(5)), - b'6' => Some(nth(6)), - b'7' => Some(nth(7)), - b'8' => Some(nth(8)), - b'9' => Some(nth(9)), - b'a' | b'A' => Some(nth(10)), - b'b' | b'B' => Some(nth(11)), - b'c' | b'C' => Some(nth(12)), - b'd' | b'D' => Some(nth(13)), - b'e' | b'E' => Some(nth(14)), - b'f' | b'F' => Some(nth(15)), - _ => None, - } -} - -impl FromRadix16 for Integer -where - I: Zero + One + AddAssign + MulAssign, -{ - fn from_radix_16(text: &[u8]) -> (Self, usize) { - let mut index = 0; - let mut number = I::zero(); - while index != text.len() { - if let Some(digit) = ascii_to_hexdigit(text[index]) { - number *= nth(16); - number += digit; - index += 1; - } else { - break; - } - } - (Integer(number), index) - } -} - -impl FromRadix16Checked for Integer -where - I: Zero + One + FromRadix16 + CheckedMul + CheckedAdd + MaxNumDigits, -{ - fn from_radix_16_checked(text: &[u8]) -> (Option, usize) { - let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; - let (number, mut index) = I::from_radix_16(&text[..min(text.len(), max_safe_digits)]); - let mut number = Some(number); - // We parsed the digits, which do not need checking now lets see the next one: - while index != text.len() { - if let Some(digit) = ascii_to_hexdigit(text[index]) { - number = number.and_then(|n| n.checked_mul(&nth(16))); - number = number.and_then(|n| n.checked_add(&digit)); - index += 1; - } else { - break; - } - } - (number.map(Integer), index) - } -} - -impl MaxNumDigits for Integer -where - I: Bounded + Zero + DivAssign + Ord + Copy, -{ - /// Returns the maximum number of digits a nonnegative representation of `I` can have depending - /// on `radix`. - fn max_num_digits(radix: Integer) -> usize { - let mut max = I::max_value(); - let mut d = 0; - while max > I::zero() { - d += 1; - max /= radix.0; - } - d - } - - /// Returns the maximum number of digits a negative representation of `I` can have depending - /// on `radix`. - fn max_num_digits_negative(radix: Integer) -> usize { - let mut min = I::min_value(); - let mut d = 0; - while min < I::zero() { - d += 1; - min /= radix.0; - } - d - } -} \ No newline at end of file +use core::{ + cmp::{max, min}, + ops::{AddAssign, DivAssign, MulAssign, SubAssign}, +}; + +use num_traits::{Bounded, CheckedAdd, CheckedMul, CheckedSub, One, Zero}; + +use crate::{ + FromDigit, FromRadix10, FromRadix10Checked, FromRadix10Signed, FromRadix10SignedChecked, + FromRadix16, FromRadix16Checked, MaxNumDigits, Sign, +}; + +/// Wrapper which implements the traits [`crate::FromRadix10`], [`crate::FromRadix10Checked`], +/// [`crate::FromRadix16`] and [`crate::FromRadix16Checked`] for any inductive type. I.e. a type +/// implementing [`One`], [`Zero`], `+=` and `*=`. +pub struct Integer(pub I); + +impl FromRadix10 for Integer +where + I: Zero + One + AddAssign + MulAssign, +{ + fn from_radix_10(text: &[u8]) -> (Self, usize) { + let mut index = 0; + let mut number = I::zero(); + while index != text.len() { + if let Some(digit) = Integer::::from_digit(text[index]) { + number *= nth(10); + number += digit.0; + index += 1; + } else { + break; + } + } + (Integer(number), index) + } +} + +impl FromRadix10Signed for Integer +where + I: Zero + One + AddAssign + SubAssign + MulAssign, +{ + fn from_radix_10_signed(text: &[u8]) -> (Self, usize) { + let mut index; + let mut number = I::zero(); + + let (sign, offset) = text + .first() + .and_then(|&byte| Sign::try_from(byte)) + .map(|sign| (sign, 1)) + .unwrap_or((Sign::Plus, 0)); + + index = offset; + + // Having two dedicated loops for both the negative and the nonnegative case is rather + // verbose, yet performed up to 40% better then a more terse single loop with + // `number += digit * signum`. + + match sign { + Sign::Plus => { + while index != text.len() { + if let Some(digit) = Integer::::from_digit(text[index]) { + number *= nth(10); + number += digit.0; + index += 1; + } else { + break; + } + } + } + Sign::Minus => { + while index != text.len() { + if let Some(digit) = Integer::::from_digit(text[index]) { + number *= nth(10); + number -= digit.0; + index += 1; + } else { + break; + } + } + } + } + + (Integer(number), index) + } +} + +impl FromDigit for Integer +where + I: Zero + One, +{ + fn from_digit(digit: u8) -> Option { + match digit { + b'0' => Some(Integer(nth(0))), + b'1' => Some(Integer(nth(1))), + b'2' => Some(Integer(nth(2))), + b'3' => Some(Integer(nth(3))), + b'4' => Some(Integer(nth(4))), + b'5' => Some(Integer(nth(5))), + b'6' => Some(Integer(nth(6))), + b'7' => Some(Integer(nth(7))), + b'8' => Some(Integer(nth(8))), + b'9' => Some(Integer(nth(9))), + _ => None, + } + } +} + +// At least for primitive types this function does not incur runtime costs, since it is only called +// with constants +fn nth(n: u8) -> I +where + I: Zero + One, +{ + let mut i = I::zero(); + for _ in 0..n { + i = i + I::one(); + } + i +} + +impl FromRadix10SignedChecked for Integer +where + I: Zero + + One + + AddAssign + + MulAssign + + SubAssign + + CheckedAdd + + CheckedSub + + CheckedMul + + MaxNumDigits, +{ + fn from_radix_10_signed_checked(text: &[u8]) -> (Option, usize) { + let mut index; + let mut number = I::zero(); + + let (sign, offset) = text + .first() + .and_then(|&byte| Sign::try_from(byte)) + .map(|sign| (sign, 1)) + .unwrap_or((Sign::Plus, 0)); + + index = offset; + + // Having two dedicated loops for both the negative and the nonnegative case is rather + // verbose, yet performed up to 40% better then a more terse single loop with + // `number += digit * signum`. + + match sign { + Sign::Plus => { + let max_safe_digits = max(1, I::max_num_digits(nth(10))) - 1; + let max_safe_index = min(text.len(), max_safe_digits + offset); + while index != max_safe_index { + if let Some(digit) = Integer::::from_digit(text[index]) { + number *= nth(10); + number += digit.0; + index += 1; + } else { + break; + } + } + // We parsed the digits, which do not need checking now lets see the next one: + let mut number = Some(number); + while index != text.len() { + if let Some(digit) = Integer::::from_digit(text[index]) { + number = number.and_then(|n| n.checked_mul(&nth(10))); + number = number.and_then(|n| n.checked_add(&digit.0)); + index += 1; + } else { + break; + } + } + (number.map(Integer), index) + } + Sign::Minus => { + let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; + let max_safe_index = min(text.len(), max_safe_digits + offset); + while index != max_safe_index { + if let Some(digit) = Integer::::from_digit(text[index]) { + number *= nth(10); + number -= digit.0; + index += 1; + } else { + break; + } + } + // We parsed the digits, which do not need checking now lets see the next one: + let mut number = Some(number); + while index != text.len() { + if let Some(digit) = Integer::::from_digit(text[index]) { + number = number.and_then(|n| n.checked_mul(&nth(10))); + number = number.and_then(|n| n.checked_sub(&digit.0)); + index += 1; + } else { + break; + } + } + (number.map(Integer), index) + } + } + } +} + +impl FromRadix10Checked for Integer +where + I: Zero + One + FromRadix10 + CheckedMul + CheckedAdd + MaxNumDigits, +{ + fn from_radix_10_checked(text: &[u8]) -> (Option, usize) { + let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; + let (number, mut index) = I::from_radix_10(&text[..min(text.len(), max_safe_digits)]); + let mut number = Some(number); + // We parsed the digits, which do not need checking now lets see the next one: + while index != text.len() { + if let Some(digit) = Integer::::from_digit(text[index]) { + number = number.and_then(|n| n.checked_mul(&nth(10))); + number = number.and_then(|n| n.checked_add(&digit.0)); + index += 1; + } else { + break; + } + } + (number.map(Integer), index) + } +} + +/// Converts an ascii character to digit +fn ascii_to_hexdigit(character: u8) -> Option +where + I: Zero + One, +{ + match character { + b'0' => Some(nth(0)), + b'1' => Some(nth(1)), + b'2' => Some(nth(2)), + b'3' => Some(nth(3)), + b'4' => Some(nth(4)), + b'5' => Some(nth(5)), + b'6' => Some(nth(6)), + b'7' => Some(nth(7)), + b'8' => Some(nth(8)), + b'9' => Some(nth(9)), + b'a' | b'A' => Some(nth(10)), + b'b' | b'B' => Some(nth(11)), + b'c' | b'C' => Some(nth(12)), + b'd' | b'D' => Some(nth(13)), + b'e' | b'E' => Some(nth(14)), + b'f' | b'F' => Some(nth(15)), + _ => None, + } +} + +impl FromRadix16 for Integer +where + I: Zero + One + AddAssign + MulAssign, +{ + fn from_radix_16(text: &[u8]) -> (Self, usize) { + let mut index = 0; + let mut number = I::zero(); + while index != text.len() { + if let Some(digit) = ascii_to_hexdigit(text[index]) { + number *= nth(16); + number += digit; + index += 1; + } else { + break; + } + } + (Integer(number), index) + } +} + +impl FromRadix16Checked for Integer +where + I: Zero + One + FromRadix16 + CheckedMul + CheckedAdd + MaxNumDigits, +{ + fn from_radix_16_checked(text: &[u8]) -> (Option, usize) { + let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; + let (number, mut index) = I::from_radix_16(&text[..min(text.len(), max_safe_digits)]); + let mut number = Some(number); + // We parsed the digits, which do not need checking now lets see the next one: + while index != text.len() { + if let Some(digit) = ascii_to_hexdigit(text[index]) { + number = number.and_then(|n| n.checked_mul(&nth(16))); + number = number.and_then(|n| n.checked_add(&digit)); + index += 1; + } else { + break; + } + } + (number.map(Integer), index) + } +} + +impl MaxNumDigits for Integer +where + I: Bounded + Zero + DivAssign + Ord + Copy, +{ + /// Returns the maximum number of digits a nonnegative representation of `I` can have depending + /// on `radix`. + fn max_num_digits(radix: Integer) -> usize { + let mut max = I::max_value(); + let mut d = 0; + while max > I::zero() { + d += 1; + max /= radix.0; + } + d + } + + /// Returns the maximum number of digits a negative representation of `I` can have depending + /// on `radix`. + fn max_num_digits_negative(radix: Integer) -> usize { + let mut min = I::min_value(); + let mut d = 0; + while min < I::zero() { + d += 1; + min /= radix.0; + } + d + } +} diff --git a/src/lib.rs b/src/lib.rs index 9a8f030..9f9edae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ use num_traits::Signed; mod builtin; mod integer; -pub use integer::{ascii_to_digit, Integer}; +pub use integer::Integer; /// Parses an integer from a slice. /// @@ -328,6 +328,38 @@ impl Sign { } } +/// Construct an instance of a numerical type using the byte representation of a radix 10 digit, +/// e.g. b'7' -> 7. +pub trait FromDigit: Sized { + /// Convert ASCII digit (e.g. b'7) into numeric representation (`7`). `None` if the character + /// given does not represent a digit in ASCII. + /// + /// # Example + /// + /// ``` + /// use atoi::FromDigit; + /// assert_eq!(Some(5), u32::from_digit(b'5')); + /// assert_eq!(None, u32::from_digit(b'x')); + /// ``` + fn from_digit(digit: u8) -> Option; +} + +/// Converts an ascii character to digit +/// +/// # Example +/// +/// ``` +/// use atoi::ascii_to_digit; +/// assert_eq!(Some(5), ascii_to_digit(b'5')); +/// assert_eq!(None, ascii_to_digit::(b'x')); +/// ``` +pub fn ascii_to_digit(digit: u8) -> Option +where + I: FromDigit, +{ + I::from_digit(digit) +} + #[cfg(test)] mod test {