use core::fmt;
use crate::core_type::{ParseError, D38};
#[cfg(feature = "alloc")]
extern crate alloc;
impl<const SCALE: u32> fmt::LowerExp for D38<SCALE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_exp(self.0, SCALE, false, f)
}
}
impl<const SCALE: u32> fmt::UpperExp for D38<SCALE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_exp(self.0, SCALE, true, f)
}
}
fn format_exp(raw: i128, scale: u32, upper: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let exp_char = if upper { 'E' } else { 'e' };
if raw == 0 {
return write!(f, "0{exp_char}0");
}
let negative = raw < 0;
let mag: u128 = raw.unsigned_abs();
let mut buf = [0u8; 40];
let mut len = 0usize;
let mut n = mag;
while n > 0 {
let digit = (n % 10) as u8;
buf[len] = b'0' + digit;
len += 1;
n /= 10;
}
buf[..len].reverse();
let digits = &buf[..len];
let exp: i32 = (len as i32 - 1) - scale as i32;
let mut frac_end = len;
while frac_end > 1 && digits[frac_end - 1] == b'0' {
frac_end -= 1;
}
let mantissa_int = digits[0] as char;
let mantissa_frac = &digits[1..frac_end];
if negative {
f.write_str("-")?;
}
if mantissa_frac.is_empty() {
write!(f, "{mantissa_int}{exp_char}{exp}")
} else {
f.write_fmt(format_args!("{mantissa_int}."))?;
let frac_str = core::str::from_utf8(mantissa_frac).map_err(|_| fmt::Error)?;
write!(f, "{frac_str}{exp_char}{exp}")
}
}
pub(crate) fn parse_decimal_bits<const SCALE: u32>(s: &str) -> Result<i128, ParseError> {
parse_decimal::<SCALE>(s).map(super::core_type::D38::to_bits)
}
fn parse_decimal<const SCALE: u32>(s: &str) -> Result<D38<SCALE>, ParseError> {
if s.is_empty() {
return Err(ParseError::Empty);
}
let bytes = s.as_bytes();
let mut idx = 0usize;
let negative = match bytes[0] {
b'-' => {
idx += 1;
true
}
b'+' => {
idx += 1;
false
}
_ => false,
};
if idx == bytes.len() {
return Err(ParseError::SignOnly);
}
let mut dot_pos: Option<usize> = None;
{
let mut i = idx;
while i < bytes.len() {
let c = bytes[i];
match c {
b'0'..=b'9' => {}
b'.' => {
if dot_pos.is_some() {
return Err(ParseError::InvalidChar);
}
dot_pos = Some(i);
}
b'e' | b'E' => {
return Err(ParseError::ScientificNotation);
}
_ => return Err(ParseError::InvalidChar),
}
i += 1;
}
}
let (int_str, frac_str) = match dot_pos {
Some(p) => (&bytes[idx..p], &bytes[p + 1..]),
None => (&bytes[idx..], &[][..]),
};
if dot_pos.is_some() {
if int_str.is_empty() || frac_str.is_empty() {
return Err(ParseError::MissingDigits);
}
} else if int_str.is_empty() {
return Err(ParseError::SignOnly);
}
if int_str.len() > 1 && int_str[0] == b'0' {
return Err(ParseError::LeadingZero);
}
if frac_str.len() > SCALE as usize {
return Err(ParseError::OverlongFractional);
}
let multiplier: u128 = 10u128.pow(SCALE);
let mut int_value: u128 = 0;
for &b in int_str {
let digit = u128::from(b - b'0');
int_value = match int_value.checked_mul(10).and_then(|v| v.checked_add(digit)) {
Some(v) => v,
None => return Err(ParseError::OutOfRange),
};
}
let int_scaled = match int_value.checked_mul(multiplier) {
Some(v) => v,
None => return Err(ParseError::OutOfRange),
};
let mut frac_value: u128 = 0;
let frac_len = frac_str.len();
for &b in frac_str {
let digit = u128::from(b - b'0');
frac_value = match frac_value
.checked_mul(10)
.and_then(|v| v.checked_add(digit))
{
Some(v) => v,
None => return Err(ParseError::OutOfRange),
};
}
let pad = (SCALE as usize) - frac_len;
if pad > 0 {
let pad_factor: u128 = 10u128.pow(pad as u32);
frac_value = match frac_value.checked_mul(pad_factor) {
Some(v) => v,
None => return Err(ParseError::OutOfRange),
};
}
let combined = match int_scaled.checked_add(frac_value) {
Some(v) => v,
None => return Err(ParseError::OutOfRange),
};
let raw: i128 = if negative {
let neg_min_abs: u128 = (i128::MAX as u128) + 1;
if combined > neg_min_abs {
return Err(ParseError::OutOfRange);
}
if combined == neg_min_abs {
i128::MIN
} else {
-(combined as i128)
}
} else {
if combined > i128::MAX as u128 {
return Err(ParseError::OutOfRange);
}
combined as i128
};
Ok(D38::<SCALE>::from_bits(raw))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core_type::{D38s12, D38};
#[cfg(feature = "alloc")]
use alloc::format;
#[cfg(feature = "alloc")]
use alloc::string::ToString;
#[cfg(feature = "alloc")]
#[test]
fn display_zero_renders() {
assert_eq!(D38s12::ZERO.to_string(), "0.000000000000");
}
#[cfg(feature = "alloc")]
#[test]
fn display_one_renders() {
assert_eq!(D38s12::ONE.to_string(), "1.000000000000");
}
#[cfg(feature = "alloc")]
#[test]
fn display_one_point_five_renders() {
let v = D38s12::from_bits(1_500_000_000_000);
assert_eq!(v.to_string(), "1.500000000000");
}
#[cfg(feature = "alloc")]
#[test]
fn display_negative_renders() {
let v = D38s12::from_bits(-1_500_000_000_000);
assert_eq!(v.to_string(), "-1.500000000000");
}
#[cfg(feature = "alloc")]
#[test]
fn display_subunit_keeps_leading_zeros() {
let v = D38s12::from_bits(1_000_000_000);
assert_eq!(v.to_string(), "0.001000000000");
}
#[cfg(feature = "alloc")]
#[test]
fn display_max_does_not_panic() {
let s = D38s12::MAX.to_string();
assert_eq!(s, "170141183460469231731687303.715884105727");
}
#[cfg(feature = "alloc")]
#[test]
fn display_min_does_not_panic() {
let s = D38s12::MIN.to_string();
assert_eq!(s, "-170141183460469231731687303.715884105728");
}
#[cfg(feature = "alloc")]
#[test]
fn display_scale_zero_no_dot() {
type D0 = D38<0>;
assert_eq!(D0::ONE.to_string(), "1");
assert_eq!(D0::ZERO.to_string(), "0");
assert_eq!(D0::from_bits(-42).to_string(), "-42");
}
#[cfg(feature = "alloc")]
#[test]
fn debug_includes_scale_and_value() {
let v = D38s12::from_bits(1_500_000_000_000);
let debug_str = format!("{v:?}");
assert_eq!(debug_str, "D38<12>(1.500000000000)");
}
#[cfg(feature = "alloc")]
#[test]
fn debug_other_scale() {
type D6 = D38<6>;
let v = D6::ZERO;
assert_eq!(format!("{v:?}"), "D38<6>(0.000000)");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_exp_one() {
let v = D38s12::ONE;
assert_eq!(format!("{v:e}"), "1e0");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_exp_one_point_five() {
let v = D38s12::from_bits(1_500_000_000_000);
assert_eq!(format!("{v:e}"), "1.5e0");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_exp_fifteen() {
let v = D38s12::from_bits(15_000_000_000_000);
assert_eq!(format!("{v:e}"), "1.5e1");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_exp_zero() {
assert_eq!(format!("{:e}", D38s12::ZERO), "0e0");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_exp_subunit_negative_exponent() {
let v = D38s12::from_bits(1_500_000_000);
assert_eq!(format!("{v:e}"), "1.5e-3");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_exp_negative() {
let v = D38s12::from_bits(-1_500_000_000_000);
assert_eq!(format!("{v:e}"), "-1.5e0");
}
#[cfg(feature = "alloc")]
#[test]
fn upper_exp_uses_capital_e() {
let v = D38s12::from_bits(1_500_000_000_000);
assert_eq!(format!("{v:E}"), "1.5E0");
}
#[cfg(feature = "alloc")]
#[test]
fn lower_hex_is_storage() {
assert_eq!(format!("{:x}", D38s12::ONE), "e8d4a51000");
}
#[cfg(feature = "alloc")]
#[test]
fn upper_hex_is_storage() {
assert_eq!(format!("{:X}", D38s12::ONE), "E8D4A51000");
}
#[cfg(feature = "alloc")]
#[test]
fn octal_zero() {
assert_eq!(format!("{:o}", D38s12::ZERO), "0");
}
#[cfg(feature = "alloc")]
#[test]
fn binary_one() {
let s = format!("{:b}", D38s12::ONE);
assert_eq!(s, "1110100011010100101001010001000000000000");
}
#[cfg(feature = "alloc")]
#[test]
fn parse_error_display_messages() {
assert_eq!(ParseError::Empty.to_string(), "empty input");
assert_eq!(
ParseError::SignOnly.to_string(),
"sign with no digits"
);
assert_eq!(
ParseError::LeadingZero.to_string(),
"redundant leading zero in integer part"
);
assert_eq!(
ParseError::OverlongFractional.to_string(),
"fractional part exceeds SCALE digits"
);
assert_eq!(
ParseError::ScientificNotation.to_string(),
"scientific notation not accepted"
);
assert_eq!(
ParseError::InvalidChar.to_string(),
"invalid character"
);
assert_eq!(
ParseError::OutOfRange.to_string(),
"value out of representable range"
);
assert_eq!(
ParseError::MissingDigits.to_string(),
"decimal point with no adjacent digits"
);
}
#[test]
fn from_str_zero() {
let v: D38s12 = "0".parse().unwrap();
assert_eq!(v, D38s12::ZERO);
let v: D38s12 = "0.0".parse().unwrap();
assert_eq!(v, D38s12::ZERO);
}
#[test]
fn from_str_one() {
let v: D38s12 = "1".parse().unwrap();
assert_eq!(v, D38s12::ONE);
let v: D38s12 = "1.0".parse().unwrap();
assert_eq!(v, D38s12::ONE);
}
#[test]
fn from_str_one_point_one_parses_exactly() {
let v: D38s12 = "1.1".parse().unwrap();
assert_eq!(v.to_bits(), 1_100_000_000_000);
}
#[test]
fn from_str_signs() {
let neg: D38s12 = "-1.5".parse().unwrap();
assert_eq!(neg.to_bits(), -1_500_000_000_000);
let pos: D38s12 = "+1.5".parse().unwrap();
assert_eq!(pos.to_bits(), 1_500_000_000_000);
}
#[test]
fn from_str_short_fractional_pads() {
let v: D38s12 = "0.5".parse().unwrap();
assert_eq!(v.to_bits(), 500_000_000_000);
}
#[test]
fn from_str_full_scale_fractional() {
let v: D38s12 = "1.500000000000".parse().unwrap();
assert_eq!(v.to_bits(), 1_500_000_000_000);
}
#[test]
fn from_str_empty_is_err() {
let r: Result<D38s12, _> = "".parse();
assert_eq!(r, Err(ParseError::Empty));
}
#[test]
fn from_str_sign_only_is_err() {
assert_eq!("-".parse::<D38s12>(), Err(ParseError::SignOnly));
assert_eq!("+".parse::<D38s12>(), Err(ParseError::SignOnly));
}
#[test]
fn from_str_leading_zero_is_err() {
assert_eq!("01".parse::<D38s12>(), Err(ParseError::LeadingZero));
assert_eq!(
"01.5".parse::<D38s12>(),
Err(ParseError::LeadingZero)
);
assert_eq!("00".parse::<D38s12>(), Err(ParseError::LeadingZero));
}
#[test]
fn from_str_overlong_fractional_is_err() {
let r: Result<D38s12, _> = "0.1234567890123".parse();
assert_eq!(r, Err(ParseError::OverlongFractional));
}
#[test]
fn from_str_scientific_notation_is_err() {
assert_eq!(
"1e3".parse::<D38s12>(),
Err(ParseError::ScientificNotation)
);
assert_eq!(
"1.5E2".parse::<D38s12>(),
Err(ParseError::ScientificNotation)
);
}
#[test]
fn from_str_invalid_char_is_err() {
assert_eq!(
"garbage".parse::<D38s12>(),
Err(ParseError::InvalidChar)
);
assert_eq!(
"1.2x".parse::<D38s12>(),
Err(ParseError::InvalidChar)
);
assert_eq!(
"1..2".parse::<D38s12>(),
Err(ParseError::InvalidChar)
);
}
#[test]
fn from_str_missing_digits_is_err() {
assert_eq!(
".5".parse::<D38s12>(),
Err(ParseError::MissingDigits)
);
assert_eq!(
"5.".parse::<D38s12>(),
Err(ParseError::MissingDigits)
);
assert_eq!(
"-.5".parse::<D38s12>(),
Err(ParseError::MissingDigits)
);
}
#[test]
fn from_str_out_of_range_is_err() {
let r: Result<D38s12, _> = "1000000000000000000000000000".parse();
assert_eq!(r, Err(ParseError::OutOfRange));
}
#[test]
fn from_str_i128_min_boundary() {
let s = "-170141183460469231731687303.715884105728";
let v: D38s12 = s.parse().unwrap();
assert_eq!(v.to_bits(), i128::MIN);
}
#[test]
fn from_str_i128_max_boundary() {
let s = "170141183460469231731687303.715884105727";
let v: D38s12 = s.parse().unwrap();
assert_eq!(v.to_bits(), i128::MAX);
}
#[test]
fn from_str_just_above_max_overflows() {
let s = "170141183460469231731687303.715884105728";
let r: Result<D38s12, _> = s.parse();
assert_eq!(r, Err(ParseError::OutOfRange));
}
#[cfg(feature = "alloc")]
#[test]
fn round_trip_representative_values() {
let cases: &[i128] = &[
0,
1,
-1,
1_000_000_000_000, -1_000_000_000_000,
1_500_000_000_000, -1_500_000_000_000,
1_100_000_000_000, 2_200_000_000_000, 3_300_000_000_000, 1_234_567_890_123, -1_234_567_890_123,
4_567_891_234_567, 7_890_123_456_789, i128::MAX,
i128::MIN,
i128::MAX / 2,
i128::MIN / 2,
];
for &raw in cases {
let v = D38s12::from_bits(raw);
let s = v.to_string();
let parsed: D38s12 = s.parse().unwrap_or_else(|e| {
panic!("round-trip parse failed for raw={raw}, s={s:?}, err={e:?}")
});
assert_eq!(
parsed.to_bits(),
raw,
"round-trip mismatch: raw={raw}, s={s:?}, parsed_bits={}",
parsed.to_bits()
);
}
}
#[cfg(feature = "alloc")]
#[test]
fn round_trip_other_scale() {
type D6 = D38<6>;
let cases: &[i128] = &[
0,
1,
-1,
1_000_000,
-1_000_000,
1_500_000,
i128::MAX,
i128::MIN,
];
for &raw in cases {
let v = D6::from_bits(raw);
let s = v.to_string();
let parsed: D6 = s.parse().expect("round-trip parse");
assert_eq!(
parsed.to_bits(),
raw,
"round-trip mismatch at SCALE=6, raw={raw}"
);
}
}
#[cfg(feature = "alloc")]
#[test]
fn round_trip_scale_zero() {
type D0 = D38<0>;
let cases: &[i128] = &[0, 1, -1, 42, -42, i128::MAX, i128::MIN];
for &raw in cases {
let v = D0::from_bits(raw);
let s = v.to_string();
let parsed: D0 = s.parse().expect("round-trip parse");
assert_eq!(
parsed.to_bits(),
raw,
"round-trip mismatch at SCALE=0, raw={raw}"
);
}
}
}