use std::str::FromStr;
use uucore::parser::num_parser::{ExtendedParser, ExtendedParserError};
use crate::number::PreciseNumber;
use uucore::extendedbigdecimal::ExtendedBigDecimal;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseNumberError {
Float,
Nan,
}
fn compute_num_digits(input: &str, ebd: ExtendedBigDecimal) -> PreciseNumber {
let input = input.to_lowercase();
let input = input.trim_start();
let input = input.strip_prefix('+').unwrap_or(input);
if input.starts_with("0x") || input.starts_with("-0x") {
return PreciseNumber {
number: ebd,
num_integral_digits: 0,
num_fractional_digits: if input.contains('.') || input.contains('p') {
None
} else {
Some(0)
},
};
}
let parts: Vec<&str> = input.split('e').collect();
debug_assert!(parts.len() <= 2);
let (mut int_digits, mut frac_digits) = match parts[0].find('.') {
Some(i) => {
let int_digits = match i {
0 => 1,
1 if parts[0].starts_with('-') => 2,
_ => i,
};
(int_digits, parts[0].len() - i - 1)
}
None => (parts[0].len(), 0),
};
if parts.len() == 2 {
let exp = parts[1].parse::<i64>().unwrap_or(0);
if exp > 0 {
int_digits += exp.try_into().unwrap_or(0);
}
frac_digits = if exp < frac_digits as i64 {
(frac_digits as i128 - exp as i128).try_into().unwrap_or(0)
} else {
0
}
}
PreciseNumber {
number: ebd,
num_integral_digits: int_digits,
num_fractional_digits: Some(frac_digits),
}
}
impl FromStr for PreciseNumber {
type Err = ParseNumberError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let ebd = match ExtendedBigDecimal::extended_parse(input) {
Ok(ebd) => match ebd {
ExtendedBigDecimal::BigDecimal(_) | ExtendedBigDecimal::MinusZero => {
ebd
}
ExtendedBigDecimal::Infinity | ExtendedBigDecimal::MinusInfinity => {
return Ok(Self {
number: ebd,
num_integral_digits: 0,
num_fractional_digits: Some(0),
});
}
ExtendedBigDecimal::Nan | ExtendedBigDecimal::MinusNan => {
return Err(ParseNumberError::Nan);
}
},
Err(ExtendedParserError::Underflow(ebd)) => ebd, Err(_) => return Err(ParseNumberError::Float),
};
Ok(compute_num_digits(input, ebd))
}
}
#[cfg(test)]
mod tests {
use bigdecimal::BigDecimal;
use uucore::extendedbigdecimal::ExtendedBigDecimal;
use crate::number::PreciseNumber;
use crate::numberparse::ParseNumberError;
fn parse(s: &str) -> ExtendedBigDecimal {
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
.unwrap()
}
fn num_fractional_digits_is_none(s: &str) -> bool {
s.parse::<PreciseNumber>()
.unwrap()
.num_fractional_digits
.is_none()
}
#[test]
fn test_parse_minus_zero_int() {
assert_eq!(parse("-0e0"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e-0"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e+1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0.0e1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0x0"), ExtendedBigDecimal::MinusZero);
}
#[test]
fn test_parse_minus_zero_float() {
assert_eq!(parse("-0.0"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e-1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0.0e-1"), ExtendedBigDecimal::MinusZero);
}
#[test]
fn test_parse_big_int() {
assert_eq!(parse("0"), ExtendedBigDecimal::zero());
assert_eq!(parse("0.1e1"), ExtendedBigDecimal::one());
assert_eq!(parse("0.1E1"), ExtendedBigDecimal::one());
assert_eq!(
parse("1.0e1"),
ExtendedBigDecimal::BigDecimal("10".parse::<BigDecimal>().unwrap())
);
}
#[test]
fn test_parse_hexadecimal_big_int() {
assert_eq!(parse("0x0"), ExtendedBigDecimal::zero());
assert_eq!(
parse("0x10"),
ExtendedBigDecimal::BigDecimal("16".parse::<BigDecimal>().unwrap())
);
}
#[test]
fn test_parse_big_decimal() {
assert_eq!(
parse("0.0"),
ExtendedBigDecimal::BigDecimal("0.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse(".0"),
ExtendedBigDecimal::BigDecimal("0.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse("1.0"),
ExtendedBigDecimal::BigDecimal("1.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse("10e-1"),
ExtendedBigDecimal::BigDecimal("1.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse("-1e-3"),
ExtendedBigDecimal::BigDecimal("-0.001".parse::<BigDecimal>().unwrap())
);
}
#[test]
fn test_parse_inf() {
assert_eq!(parse("inf"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("infinity"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("+inf"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("+infinity"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("-inf"), ExtendedBigDecimal::MinusInfinity);
assert_eq!(parse("-infinity"), 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::Float
);
}
#[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]
#[allow(clippy::cognitive_complexity)]
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"), 3);
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"), 3);
assert_eq!(num_integral_digits("123.45e-1"), 3);
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]
#[allow(clippy::cognitive_complexity)]
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);
assert_eq!(num_fractional_digits("0xff"), 0);
assert!(num_fractional_digits_is_none("0xff.1"));
}
#[test]
fn test_parse_min_exponents() {
assert!("1e-9223372036854775807".parse::<PreciseNumber>().is_ok());
assert!("1e-9223372036854775808".parse::<PreciseNumber>().is_ok());
assert!("1e-92233720368547758080".parse::<PreciseNumber>().is_ok());
}
#[test]
fn test_parse_max_exponents() {
assert!("1e9223372036854775807".parse::<PreciseNumber>().is_ok());
assert!("1e92233720368547758070".parse::<PreciseNumber>().is_err());
}
}