use crate::constants::SCALE;
use crate::error::SolMathError;
pub fn fp(decimal: &str) -> Result<u128, SolMathError> {
if decimal.starts_with('-') {
return Err(SolMathError::DomainError);
}
let unsigned = if decimal.starts_with('+') {
&decimal[1..]
} else {
decimal
};
parse_decimal_magnitude(unsigned)
}
pub fn fp_i(decimal: &str) -> Result<i128, SolMathError> {
let (negative, unsigned) = if decimal.starts_with('-') {
(true, &decimal[1..])
} else if decimal.starts_with('+') {
(false, &decimal[1..])
} else {
(false, decimal)
};
let magnitude = parse_decimal_magnitude(unsigned)?;
if negative {
let min_magnitude = (i128::MAX as u128)
.checked_add(1)
.ok_or(SolMathError::Overflow)?;
if magnitude == min_magnitude {
Ok(i128::MIN)
} else if magnitude <= i128::MAX as u128 {
Ok(-(magnitude as i128))
} else {
Err(SolMathError::Overflow)
}
} else if magnitude <= i128::MAX as u128 {
Ok(magnitude as i128)
} else {
Err(SolMathError::Overflow)
}
}
fn parse_decimal_magnitude(decimal: &str) -> Result<u128, SolMathError> {
let bytes = decimal.as_bytes();
if bytes.is_empty() {
return Err(SolMathError::DomainError);
}
let mut whole = 0u128;
let mut frac = 0u128;
let mut frac_digits = 0u8;
let mut seen_dot = false;
let mut seen_digit = false;
for &byte in bytes {
match byte {
b'0'..=b'9' => {
seen_digit = true;
let digit = (byte - b'0') as u128;
if seen_dot {
if frac_digits < 12 {
frac = frac
.checked_mul(10)
.ok_or(SolMathError::Overflow)?
.checked_add(digit)
.ok_or(SolMathError::Overflow)?;
frac_digits += 1;
} else if digit != 0 {
return Err(SolMathError::DomainError);
}
} else {
whole = whole
.checked_mul(10)
.ok_or(SolMathError::Overflow)?
.checked_add(digit)
.ok_or(SolMathError::Overflow)?;
}
}
b'.' if !seen_dot => {
seen_dot = true;
}
_ => return Err(SolMathError::DomainError),
}
}
if !seen_digit {
return Err(SolMathError::DomainError);
}
while frac_digits < 12 {
frac = frac.checked_mul(10).ok_or(SolMathError::Overflow)?;
frac_digits += 1;
}
whole
.checked_mul(SCALE)
.and_then(|scaled| scaled.checked_add(frac))
.ok_or(SolMathError::Overflow)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::SCALE_I;
#[test]
fn parses_unsigned_decimals() {
assert_eq!(fp("0").unwrap(), 0);
assert_eq!(fp("1").unwrap(), SCALE);
assert_eq!(fp("1.5").unwrap(), SCALE + SCALE / 2);
assert_eq!(fp(".05").unwrap(), 50_000_000_000);
assert_eq!(fp("+2.000000000001").unwrap(), 2 * SCALE + 1);
}
#[test]
fn parses_signed_decimals() {
assert_eq!(fp_i("-0.25").unwrap(), -250_000_000_000);
assert_eq!(fp_i("+0.25").unwrap(), 250_000_000_000);
assert_eq!(fp_i("0").unwrap(), 0);
assert_eq!(fp_i("1").unwrap(), SCALE_I);
}
#[test]
fn rejects_ambiguous_or_lossy_inputs() {
assert_eq!(fp("").unwrap_err(), SolMathError::DomainError);
assert_eq!(fp(".").unwrap_err(), SolMathError::DomainError);
assert_eq!(fp("1.2.3").unwrap_err(), SolMathError::DomainError);
assert_eq!(fp("1e-3").unwrap_err(), SolMathError::DomainError);
assert_eq!(fp("-1").unwrap_err(), SolMathError::DomainError);
assert_eq!(
fp("0.0000000000001").unwrap_err(),
SolMathError::DomainError
);
assert_eq!(fp("0.0000000000000").unwrap(), 0);
}
#[test]
fn reports_overflow() {
assert_eq!(
fp("340282366920938463463374607.431768211456").unwrap_err(),
SolMathError::Overflow
);
assert_eq!(
fp_i("170141183460469231731687304.000000000000").unwrap_err(),
SolMathError::Overflow
);
}
}