crate::ix!();
pub fn parse_fixed_point(val: &str, decimals: i32, amount_out: *mut i64) -> bool {
const UPPER_BOUND: i64 = 1_000_000_000_000_000_000 - 1;
debug!(input = %val, decimals, "parse_fixed_point: start");
let mut mantissa: i64 = 0;
let mut exponent: i64 = 0;
let mut mantissa_tzeros: i32 = 0;
let mut mantissa_sign = false;
let mut exponent_sign = false;
let mut ptr: usize = 0;
let end = val.len();
let mut point_ofs: i32 = 0;
let bytes = val.as_bytes();
if ptr < end && bytes[ptr] == b'-' {
mantissa_sign = true;
ptr += 1;
}
if ptr < end {
match bytes[ptr] {
b'0' => ptr += 1,
b'1'..=b'9' => {
while ptr < end && bytes[ptr].is_ascii_digit() {
if !process_mantissa_digit(bytes[ptr], &mut mantissa, &mut mantissa_tzeros) {
error!("mantissa overflow while scanning integer part");
return false;
}
ptr += 1;
}
}
_ => return false,
}
} else {
return false;
}
if ptr < end && bytes[ptr] == b'.' {
ptr += 1;
if ptr < end && bytes[ptr].is_ascii_digit() {
while ptr < end && bytes[ptr].is_ascii_digit() {
if !process_mantissa_digit(bytes[ptr], &mut mantissa, &mut mantissa_tzeros) {
error!("mantissa overflow while scanning fractional part");
return false;
}
ptr += 1;
point_ofs += 1;
}
} else {
return false;
}
}
if ptr < end && (bytes[ptr] == b'e' || bytes[ptr] == b'E') {
ptr += 1;
if ptr < end && bytes[ptr] == b'+' {
ptr += 1;
} else if ptr < end && bytes[ptr] == b'-' {
exponent_sign = true;
ptr += 1;
}
if ptr < end && bytes[ptr].is_ascii_digit() {
while ptr < end && bytes[ptr].is_ascii_digit() {
if exponent > UPPER_BOUND / 10 {
error!("exponent overflow");
return false;
}
exponent = exponent * 10 + (bytes[ptr] - b'0') as i64;
ptr += 1;
}
} else {
return false;
}
}
if ptr != end {
return false;
}
if exponent_sign {
exponent = -exponent;
}
exponent = exponent - point_ofs as i64 + mantissa_tzeros as i64;
if mantissa_sign {
mantissa = -mantissa;
}
exponent += decimals as i64;
if exponent < 0 || exponent >= 18 {
return false;
}
for _ in 0..(exponent as usize) {
if mantissa > UPPER_BOUND / 10 || mantissa < -UPPER_BOUND / 10 {
error!("mantissa overflow during scaling");
return false;
}
mantissa *= 10;
}
if mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND {
return false;
}
if !amount_out.is_null() {
unsafe { *amount_out = mantissa };
}
debug!(result = mantissa, "parse_fixed_point: success");
true
}
#[cfg(test)]
mod parse_fixed_point_tests {
use super::*;
use std::ptr;
fn check(
input: &str,
decimals: i32,
expect_ok: bool,
expect_val: i64,
use_null_out: bool,
) {
let mut out: i64 = 0;
let out_ptr = if use_null_out { ptr::null_mut() } else { &mut out };
let ok = parse_fixed_point(input, decimals, out_ptr);
assert_eq!(
ok, expect_ok,
"unexpected success/failure for “{}” with {} decimals",
input, decimals
);
if expect_ok && !use_null_out {
assert_eq!(
out, expect_val,
"mismatched parsed value for “{}” with {} decimals",
input, decimals
);
}
}
#[traced_test]
fn integer_zero() {
check("0", 0, true, 0, false);
}
#[traced_test]
fn integer_positive() {
check("123456", 0, true, 123_456, false);
}
#[traced_test]
fn integer_negative() {
check("-98765", 0, true, -98_765, false);
}
#[traced_test]
fn fractional_exact_decimals() {
check("1.23", 2, true, 123, false);
}
#[traced_test]
fn fractional_leading_zero() {
check("0.01", 2, true, 1, false);
}
#[traced_test]
fn fractional_trailing_zeros() {
check("12.3400", 2, true, 1_234, false);
}
#[traced_test]
fn exponent_positive() {
check("1e3", 0, true, 1_000, false);
}
#[traced_test]
fn exponent_negative() {
check("1e-1", 1, true, 1, false);
}
#[traced_test]
fn decimal_and_exponent() {
check("1.23e2", 0, true, 123, false);
}
#[traced_test]
fn overflow_large_positive() {
check("1000000000000000000", 0, false, 0, false); }
#[traced_test]
fn overflow_large_negative() {
check("-1000000000000000000", 0, false, 0, false); }
#[traced_test]
fn underflow_small_fraction() {
check("0.0001", 2, false, 0, false); }
#[traced_test]
fn invalid_empty_string() {
check("", 0, false, 0, false);
}
#[traced_test]
fn invalid_trailing_garbage() {
check("1.0xyz", 0, false, 0, false);
}
#[traced_test]
fn null_output_pointer_still_ok() {
check("42", 0, true, 0, true);
}
}