type Flags = u32;
pub const FLAG_OVERFLOW: Flags = 0x08;
pub const FLAG_UNDERFLOW: Flags = 0x10;
pub const FLAG_INEXACT: Flags = 0x20;
#[repr(i32)]
pub enum Rounding {
ToNearest = 0x00000,
Down = 0x00001,
Up = 0x00002,
ToZero = 0x00003,
TiesAway = 0x00004,
}
impl From<i32> for Rounding {
fn from(value: i32) -> Self {
match value {
0x00000 => Rounding::ToNearest,
0x00001 => Rounding::Down,
0x00002 => Rounding::Up,
0x00003 => Rounding::ToZero,
0x00004 => Rounding::TiesAway,
_ => Rounding::ToNearest,
}
}
}
enum State {
BeginNumber,
LeadingZerosBefore,
DigitsBefore,
DigitsAfter,
ExponentSign,
ExponentLeadingZeros,
ExponentDigits,
Inf2n,
Inf3f,
Inf4i,
Inf5n,
Inf6i,
Inf7t,
Inf8y,
Nan1n,
Nan2a,
Nan3n,
}
pub enum Value {
Finite(
bool,
u128,
i32,
Flags,
),
Infinity(
bool,
),
NaN(
bool,
bool,
),
}
const MAX_STRING_DIGITS: usize = 100;
macro_rules! update_value {
($value:expr, $ch:expr, $digits:expr, $max_digits: expr, $digits_total:expr, $buffer:expr, $inexact:expr) => {{
let b = ($ch as u8) - b'0';
if $digits < $max_digits {
$value = $value * 10 + b as u128;
if $value > 0 {
$digits += 1;
}
} else {
$inexact = b > 0;
}
if $value > 0 && $digits_total < MAX_STRING_DIGITS {
$buffer[$digits_total] = b;
$digits_total += 1;
}
}};
}
macro_rules! update_exponent {
($v:expr, $c:expr) => {{
$v = $v.saturating_mul(10).saturating_add((($c as u8) - b'0') as i32);
}};
}
pub fn recognize(input: &str, max_digits: usize, rnd: Rounding) -> Value {
let mut sign = false;
let mut signaling = false;
if input.is_empty() {
return Value::NaN(sign, signaling);
}
let mut state = State::BeginNumber;
let mut exp = 0_i32;
let mut exp_base = 0_i32;
let mut exp_sign = 1_i32;
let mut val = 0_u128;
let mut inf = false;
let mut nan = false;
let mut digits = 0_usize;
let mut digits_total = 0_usize;
let mut inexact = false;
let mut buffer = [0_u8; MAX_STRING_DIGITS];
let last = input.len() - 1;
for (position, ch) in input.chars().enumerate() {
match state {
State::BeginNumber => match ch {
' ' | '\t' | '\n' | '\r' => {}
'-' => {
sign = true;
state = State::LeadingZerosBefore;
}
'+' | '0' => state = State::LeadingZerosBefore,
'1'..='9' => {
update_value!(val, ch, digits, max_digits, digits_total, buffer, inexact);
state = State::DigitsBefore;
}
'.' if position < last => state = State::DigitsAfter,
'.' if position == last => break,
'i' | 'I' if position < last => state = State::Inf2n,
'n' | 'N' if position < last => state = State::Nan2a,
's' | 'S' if position < last => {
signaling = true;
state = State::Nan1n
}
_ => return Value::NaN(sign, signaling),
},
State::LeadingZerosBefore => match ch {
'0' => {}
'1'..='9' => {
update_value!(val, ch, digits, max_digits, digits_total, buffer, inexact);
state = State::DigitsBefore;
}
'.' => state = State::DigitsAfter,
'E' | 'e' => state = State::ExponentSign,
'i' | 'I' => state = State::Inf2n,
'n' | 'N' => state = State::Nan2a,
's' | 'S' => {
signaling = true;
state = State::Nan1n
}
_ => return Value::NaN(sign, signaling),
},
State::DigitsBefore => match ch {
'0'..='9' => {
if digits == max_digits {
exp += 1;
}
update_value!(val, ch, digits, max_digits, digits_total, buffer, inexact)
}
'.' => state = State::DigitsAfter,
'E' | 'e' => state = State::ExponentSign,
_ => return Value::NaN(sign, signaling),
},
State::DigitsAfter => match ch {
'0'..='9' => {
if digits < max_digits {
exp -= 1;
}
update_value!(val, ch, digits, max_digits, digits_total, buffer, inexact);
}
'E' | 'e' if position < last => state = State::ExponentSign,
_ => return Value::NaN(sign, signaling),
},
State::ExponentSign => match ch {
'+' | '0' if position < last => state = State::ExponentLeadingZeros,
'-' if position < last => {
exp_sign = -1_i32;
state = State::ExponentLeadingZeros;
}
'1'..='9' => {
update_exponent!(exp_base, ch);
state = State::ExponentDigits;
}
_ => return Value::NaN(sign, signaling),
},
State::ExponentLeadingZeros => match ch {
'0' => {}
'1'..='9' => {
update_exponent!(exp_base, ch);
state = State::ExponentDigits;
}
_ => break,
},
State::ExponentDigits => match ch {
'0'..='9' => {
update_exponent!(exp_base, ch);
}
_ => break,
},
State::Inf2n => match ch {
'n' | 'N' if position < last => state = State::Inf3f,
_ => return Value::NaN(sign, signaling),
},
State::Inf3f => match ch {
'f' | 'F' if position == last => inf = true,
'f' | 'F' => state = State::Inf4i,
_ => return Value::NaN(sign, signaling),
},
State::Inf4i => match ch {
'i' | 'I' if position < last => state = State::Inf5n,
_ => return Value::NaN(sign, signaling),
},
State::Inf5n => match ch {
'n' | 'N' if position < last => state = State::Inf6i,
_ => return Value::NaN(sign, signaling),
},
State::Inf6i => match ch {
'i' | 'I' if position < last => state = State::Inf7t,
_ => return Value::NaN(sign, signaling),
},
State::Inf7t => match ch {
't' | 'T' if position < last => state = State::Inf8y,
_ => return Value::NaN(sign, signaling),
},
State::Inf8y => match ch {
'y' | 'Y' if position == last => {
inf = true;
break;
}
_ => return Value::NaN(sign, signaling),
},
State::Nan1n => match ch {
'n' | 'N' if position < last => state = State::Nan2a,
_ => return Value::NaN(sign, false),
},
State::Nan2a => match ch {
'a' | 'A' if position < last => state = State::Nan3n,
_ => return Value::NaN(sign, false),
},
State::Nan3n => match ch {
'n' | 'N' => {
nan = true;
break;
}
_ => return Value::NaN(sign, false),
},
}
}
if inf {
return Value::Infinity(sign);
}
if nan {
return Value::NaN(sign, signaling);
}
exp = exp.saturating_add(exp_sign.saturating_mul(exp_base));
let mut flags: Flags = 0_u32;
if digits_total > max_digits {
let mut carry = 0_u32;
let mut i = max_digits;
match rnd {
Rounding::ToNearest => {
carry = ((4 - (buffer[i] as i32)) as u32) >> 31;
if (buffer[i] == 5 && (buffer[i - 1] & 1) == 0) || exp < 0 {
if exp >= 0 {
carry = 0;
i += 1;
}
for b in &buffer[i..digits_total] {
if *b > 0 {
carry = 1;
break;
}
}
}
}
Rounding::Down => {
if sign {
for b in &buffer[i..digits_total] {
if *b > 0 {
carry = 1;
break;
}
}
}
}
Rounding::Up => {
if !sign {
for b in &buffer[i..digits_total] {
if *b > 0 {
carry = 1;
break;
}
}
}
}
Rounding::ToZero => {
carry = 0;
}
Rounding::TiesAway => {
carry = ((4 - (buffer[i] as i32)) as u32) >> 31;
if exp < 0 {
for b in &buffer[i..digits_total] {
if *b > 0 {
carry = 1;
break;
}
}
}
}
}
val += carry as u128;
if inexact {
flags |= FLAG_INEXACT;
}
}
Value::Finite(sign, val, exp, flags)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rounding_from_u32() {
assert_eq!(0, Rounding::from(0) as u32);
assert_eq!(1, Rounding::from(1) as u32);
assert_eq!(2, Rounding::from(2) as u32);
assert_eq!(3, Rounding::from(3) as u32);
assert_eq!(4, Rounding::from(4) as u32);
assert_eq!(0, Rounding::from(5) as u32);
assert_eq!(0, Rounding::from(100) as u32);
}
}