use num_traits::{
ops::checked::{CheckedAdd, CheckedMul},
Bounded, CheckedSub, One, Signed, Zero,
};
use std::{
cmp::{max, min},
ops::{AddAssign, DivAssign, MulAssign, SubAssign},
};
pub fn atoi<I>(text: &[u8]) -> Option<I>
where
I: FromRadix10Checked,
{
match I::from_radix_10_checked(text) {
(_, 0) | (None, _) => None,
(Some(n), _) => Some(n),
}
}
pub trait FromRadix10: Sized {
fn from_radix_10(_: &[u8]) -> (Self, usize);
}
pub trait FromRadix10Checked: FromRadix10 {
fn from_radix_10_checked(_: &[u8]) -> (Option<Self>, usize);
}
pub trait FromRadix16: Sized {
fn from_radix_16(_: &[u8]) -> (Self, usize);
}
pub trait FromRadix16Checked: FromRadix16 {
fn from_radix_16_checked(_: &[u8]) -> (Option<Self>, usize);
}
pub trait FromRadix10Signed: Sized {
fn from_radix_10_signed(_: &[u8]) -> (Self, usize);
}
pub trait FromRadix10SignedChecked: FromRadix10Signed {
fn from_radix_10_signed_checked(_: &[u8]) -> (Option<Self>, usize);
}
pub trait MaxNumDigits {
fn max_num_digits(radix: Self) -> usize;
fn max_num_digits_negative(radix: Self) -> usize;
}
impl<I> MaxNumDigits for I
where
I: Bounded + Zero + DivAssign + Ord + Copy,
{
fn max_num_digits(radix: I) -> usize {
let mut max = I::max_value();
let mut d = 0;
while max > I::zero() {
d += 1;
max /= radix;
}
d
}
fn max_num_digits_negative(radix: I) -> usize {
let mut min = I::min_value();
let mut d = 0;
while min < I::zero() {
d += 1;
min /= radix;
}
d
}
}
pub fn ascii_to_digit<I>(character: u8) -> Option<I>
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,
}
}
impl<I> FromRadix10 for I
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;
}
}
(number, index)
}
}
impl<I> FromRadix10Signed for I
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;
match sign {
Sign::Plus => {
while index != text.len() {
if let Some(digit) = ascii_to_digit::<I>(text[index]) {
number *= nth(10);
number += digit;
index += 1;
} else {
break;
}
}
}
Sign::Minus => {
while index != text.len() {
if let Some(digit) = ascii_to_digit::<I>(text[index]) {
number *= nth(10);
number -= digit;
index += 1;
} else {
break;
}
}
}
}
(number, index)
}
}
impl<I> FromRadix10SignedChecked for I
where
I: Zero
+ One
+ AddAssign
+ MulAssign
+ SubAssign
+ CheckedAdd
+ CheckedSub
+ CheckedMul
+ MaxNumDigits,
{
fn from_radix_10_signed_checked(text: &[u8]) -> (Option<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;
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::<I>(text[index]) {
number *= nth(10);
number += digit;
index += 1;
} else {
break;
}
}
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, 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::<I>(text[index]) {
number *= nth(10);
number -= digit;
index += 1;
} else {
break;
}
}
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, index)
}
}
}
}
impl<I> FromRadix10Checked for I
where
I: Zero + One + FromRadix10 + CheckedMul + CheckedAdd + MaxNumDigits,
{
fn from_radix_10_checked(text: &[u8]) -> (Option<I>, 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);
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, index)
}
}
fn ascii_to_hexdigit<I>(character: u8) -> Option<I>
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<I> FromRadix16 for I
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;
}
}
(number, index)
}
}
impl<I> FromRadix16Checked for I
where
I: Zero + One + FromRadix16 + CheckedMul + CheckedAdd + MaxNumDigits,
{
fn from_radix_16_checked(text: &[u8]) -> (Option<I>, 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);
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, index)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Sign {
Plus,
Minus,
}
impl Sign {
pub fn try_from(byte: u8) -> Option<Sign> {
match byte {
b'+' => Some(Sign::Plus),
b'-' => Some(Sign::Minus),
_ => None,
}
}
pub fn signum<I>(self) -> I
where
I: Signed,
{
match self {
Sign::Plus => I::one(),
Sign::Minus => -I::one(),
}
}
}
fn nth<I>(n: u8) -> I
where
I: Zero + One,
{
let mut i = I::zero();
for _ in 0..n {
i = i + I::one();
}
i
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn max_digits() {
assert_eq!(10, i32::max_num_digits(10));
assert_eq!(10, u32::max_num_digits(10));
assert_eq!(19, i64::max_num_digits(10));
assert_eq!(20, u64::max_num_digits(10));
assert_eq!(3, u8::max_num_digits(10));
assert_eq!(3, i8::max_num_digits(10));
}
#[test]
fn max_digits_negative() {
assert_eq!(10, i32::max_num_digits_negative(10));
assert_eq!(0, u32::max_num_digits_negative(10));
assert_eq!(19, i64::max_num_digits_negative(10));
assert_eq!(0, u64::max_num_digits_negative(10));
assert_eq!(0, u8::max_num_digits_negative(10));
assert_eq!(3, i8::max_num_digits_negative(10));
}
#[test]
fn checked_parsing() {
assert_eq!((Some(255), 3), u8::from_radix_10_checked(b"255"));
assert_eq!((None, 3), u8::from_radix_10_checked(b"256"));
assert_eq!((None, 4), u8::from_radix_10_checked(b"1000"));
assert_eq!((Some(25), 2), u8::from_radix_10_checked(b"25"));
assert_eq!((Some(25), 2), u8::from_radix_10_checked(b"25Blub"));
}
#[test]
fn checked_parsing_radix_16() {
assert_eq!((Some(255), 2), u8::from_radix_16_checked(b"FF"));
assert_eq!((None, 3), u8::from_radix_16_checked(b"100"));
assert_eq!((None, 4), u8::from_radix_16_checked(b"1000"));
assert_eq!((Some(25), 2), u8::from_radix_16_checked(b"19"));
assert_eq!((Some(25), 2), u8::from_radix_16_checked(b"19!Blub"));
}
}