use std::str::FromStr;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_bigint::Sign;
use num_traits::Num;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseNumberError {
Float,
Nan,
Hex,
}
fn is_minus_zero_int(s: &str, n: &BigInt) -> bool {
s.starts_with('-') && n == &BigInt::zero()
}
fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool {
s.starts_with('-') && x == &BigDecimal::zero()
}
fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberError> {
match s.parse::<BigInt>() {
Ok(n) => {
if is_minus_zero_int(s, &n) {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
s.len(),
0,
))
} else {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(n)),
s.len(),
0,
))
}
}
Err(_) => {
if s.eq_ignore_ascii_case("inf") {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::Infinity),
0,
0,
))
} else if s.eq_ignore_ascii_case("-inf") {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusInfinity),
0,
0,
))
} else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") {
Err(ParseNumberError::Nan)
} else {
Err(ParseNumberError::Float)
}
}
}
}
fn parse_exponent_no_decimal(s: &str, j: usize) -> Result<PreciseNumber, ParseNumberError> {
let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?;
let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
let num_integral_digits = if is_minus_zero_float(s, &x) {
2
} else {
let total = j as i64 + exponent;
let result = if total < 1 {
1
} else {
total.try_into().unwrap()
};
if x.sign() == Sign::Minus {
result + 1
} else {
result
}
};
let num_fractional_digits = if exponent < 0 { -exponent as usize } else { 0 };
if exponent < 0 {
if is_minus_zero_float(s, &x) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
num_integral_digits,
num_fractional_digits,
))
}
} else {
let zeros = "0".repeat(exponent.try_into().unwrap());
let expanded = [&s[0..j], &zeros].concat();
parse_no_decimal_no_exponent(&expanded)
}
}
fn parse_decimal_no_exponent(s: &str, i: usize) -> Result<PreciseNumber, ParseNumberError> {
let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
let num_integral_digits = if s.starts_with("-.") { i + 1 } else { i };
let num_fractional_digits = s.len() - (i + 1);
if is_minus_zero_float(s, &x) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
num_integral_digits,
num_fractional_digits,
))
}
}
fn parse_decimal_and_exponent(
s: &str,
i: usize,
j: usize,
) -> Result<PreciseNumber, ParseNumberError> {
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?;
let val: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
let num_integral_digits = {
let minimum: usize = {
let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?;
if integral_part.is_sign_negative() {
2
} else {
1
}
};
let total = if s.starts_with("-.") {
i as i64 + exponent + 1
} else {
i as i64 + exponent
};
if total < minimum as i64 {
minimum
} else {
total.try_into().unwrap()
}
};
let num_fractional_digits = if num_digits_between_decimal_point_and_e < exponent {
0
} else {
(num_digits_between_decimal_point_and_e - exponent)
.try_into()
.unwrap()
};
if num_digits_between_decimal_point_and_e <= exponent {
if is_minus_zero_float(s, &val) {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
let zeros: String = "0".repeat(
(exponent - num_digits_between_decimal_point_and_e)
.try_into()
.unwrap(),
);
let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat();
let n = expanded
.parse::<BigInt>()
.map_err(|_| ParseNumberError::Float)?;
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(n)),
num_integral_digits,
num_fractional_digits,
))
}
} else if is_minus_zero_float(s, &val) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(val)),
num_integral_digits,
num_fractional_digits,
))
}
}
fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
let (is_neg, s) = if s.starts_with('-') {
(true, &s[3..])
} else {
(false, &s[2..])
};
if s.starts_with('-') || s.starts_with('+') {
return Err(ParseNumberError::Float);
}
let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?;
match (is_neg, num == BigInt::zero()) {
(true, true) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
2,
0,
)),
(true, false) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(-num)),
0,
0,
)),
(false, _) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(num)),
0,
0,
)),
}
}
impl FromStr for PreciseNumber {
type Err = ParseNumberError;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
s = s.trim_start();
if s.starts_with('+') {
s = &s[1..];
}
if let Some(i) = s.to_lowercase().find("0x") {
if i <= 1 {
return parse_hexadecimal(s);
}
}
match (s.find('.'), s.find('e')) {
(None, None) => parse_no_decimal_no_exponent(s),
(None, Some(j)) => parse_exponent_no_decimal(s, j),
(Some(i), None) => parse_decimal_no_exponent(s, i),
(Some(i), Some(j)) if i < j => parse_decimal_and_exponent(s, i, j),
_ => Err(ParseNumberError::Float),
}
}
}
#[cfg(test)]
mod tests {
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
use crate::numberparse::ParseNumberError;
fn parse(s: &str) -> Number {
s.parse::<PreciseNumber>().unwrap().number
}
fn num_integral_digits(s: &str) -> usize {
s.parse::<PreciseNumber>().unwrap().num_integral_digits
}
fn num_fractional_digits(s: &str) -> usize {
s.parse::<PreciseNumber>().unwrap().num_fractional_digits
}
#[test]
fn test_parse_minus_zero_int() {
assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero));
}
#[test]
fn test_parse_minus_zero_float() {
assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero));
assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero));
assert_eq!(
parse("-0.0e-1"),
Number::Float(ExtendedBigDecimal::MinusZero)
);
}
#[test]
fn test_parse_big_int() {
assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero()));
assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one()));
assert_eq!(
parse("1.0e1"),
Number::Int(ExtendedBigInt::BigInt("10".parse::<BigInt>().unwrap()))
);
}
#[test]
fn test_parse_hexadecimal_big_int() {
assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero()));
assert_eq!(
parse("0x10"),
Number::Int(ExtendedBigInt::BigInt("16".parse::<BigInt>().unwrap()))
);
}
#[test]
fn test_parse_big_decimal() {
assert_eq!(
parse("0.0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"0.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse(".0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"0.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse("1.0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"1.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse("10e-1"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"1.0".parse::<BigDecimal>().unwrap()
))
);
assert_eq!(
parse("-1e-3"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"-0.001".parse::<BigDecimal>().unwrap()
))
);
}
#[test]
fn test_parse_inf() {
assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity));
assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity));
assert_eq!(
parse("-inf"),
Number::Float(ExtendedBigDecimal::MinusInfinity)
);
}
#[test]
fn test_parse_invalid_float() {
assert_eq!(
"1.2.3".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
assert_eq!(
"1e2e3".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
assert_eq!(
"1e2.3".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
assert_eq!(
"-+-1".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Float
);
}
#[test]
fn test_parse_invalid_hex() {
assert_eq!(
"0xg".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Hex
);
}
#[test]
fn test_parse_invalid_nan() {
assert_eq!(
"nan".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"NAN".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"NaN".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"nAn".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
assert_eq!(
"-nan".parse::<PreciseNumber>().unwrap_err(),
ParseNumberError::Nan
);
}
#[test]
fn test_num_integral_digits() {
assert_eq!(num_integral_digits("123"), 3);
assert_eq!(num_integral_digits("123.45"), 3);
assert_eq!(num_integral_digits("-0.1"), 2);
assert_eq!(num_integral_digits("-.1"), 2);
assert_eq!(num_integral_digits("123e4"), 3 + 4);
assert_eq!(num_integral_digits("123e-4"), 1);
assert_eq!(num_integral_digits("-1e-3"), 2);
assert_eq!(num_integral_digits("123.45e6"), 3 + 6);
assert_eq!(num_integral_digits("123.45e-6"), 1);
assert_eq!(num_integral_digits("123.45e-1"), 2);
assert_eq!(num_integral_digits("-0.1e0"), 2);
assert_eq!(num_integral_digits("-0.1e2"), 4);
assert_eq!(num_integral_digits("-.1e0"), 2);
assert_eq!(num_integral_digits("-.1e2"), 4);
assert_eq!(num_integral_digits("-1.e-3"), 2);
assert_eq!(num_integral_digits("-1.0e-4"), 2);
assert_eq!(num_integral_digits("-0e0"), 2);
assert_eq!(num_integral_digits("-0e-0"), 2);
assert_eq!(num_integral_digits("-0e1"), 3);
assert_eq!(num_integral_digits("-0e+1"), 3);
assert_eq!(num_integral_digits("-0.0e1"), 3);
assert_eq!(num_integral_digits("-0.0"), 2);
assert_eq!(num_integral_digits("-0e-1"), 2);
assert_eq!(num_integral_digits("-0.0e-1"), 2);
assert_eq!(num_integral_digits("0xff"), 0);
}
#[test]
fn test_num_fractional_digits() {
assert_eq!(num_fractional_digits("123"), 0);
assert_eq!(num_fractional_digits("0xff"), 0);
assert_eq!(num_fractional_digits("123.45"), 2);
assert_eq!(num_fractional_digits("-0.1"), 1);
assert_eq!(num_fractional_digits("-.1"), 1);
assert_eq!(num_fractional_digits("123e4"), 0);
assert_eq!(num_fractional_digits("123e-4"), 4);
assert_eq!(num_fractional_digits("123e-1"), 1);
assert_eq!(num_fractional_digits("-1e-3"), 3);
assert_eq!(num_fractional_digits("123.45e6"), 0);
assert_eq!(num_fractional_digits("123.45e1"), 1);
assert_eq!(num_fractional_digits("123.45e-6"), 8);
assert_eq!(num_fractional_digits("123.45e-1"), 3);
assert_eq!(num_fractional_digits("-0.1e0"), 1);
assert_eq!(num_fractional_digits("-0.1e2"), 0);
assert_eq!(num_fractional_digits("-.1e0"), 1);
assert_eq!(num_fractional_digits("-.1e2"), 0);
assert_eq!(num_fractional_digits("-1.e-3"), 3);
assert_eq!(num_fractional_digits("-1.0e-4"), 5);
assert_eq!(num_fractional_digits("-0e0"), 0);
assert_eq!(num_fractional_digits("-0e-0"), 0);
assert_eq!(num_fractional_digits("-0e1"), 0);
assert_eq!(num_fractional_digits("-0e+1"), 0);
assert_eq!(num_fractional_digits("-0.0e1"), 0);
assert_eq!(num_fractional_digits("-0.0"), 1);
assert_eq!(num_fractional_digits("-0e-1"), 1);
assert_eq!(num_fractional_digits("-0.0e-1"), 2);
}
}