use crate::{
constants::MAX_STR_BUFFER_SIZE,
error::Error,
ops::array::{add_by_internal, add_one_internal, div_by_u32, is_all_zero, mul_by_10, mul_by_u32},
Decimal,
};
use arrayvec::{ArrayString, ArrayVec};
use alloc::{string::String, vec::Vec};
use core::fmt;
pub(crate) fn to_str_internal(
value: &Decimal,
append_sign: bool,
precision: Option<usize>,
) -> ArrayString<[u8; MAX_STR_BUFFER_SIZE]> {
let scale = value.scale() as usize;
let mut chars = ArrayVec::<[_; MAX_STR_BUFFER_SIZE]>::new();
let mut working = value.mantissa_array3();
while !is_all_zero(&working) {
let remainder = div_by_u32(&mut working, 10u32);
chars.push(char::from(b'0' + remainder as u8));
}
while scale > chars.len() {
chars.push('0');
}
let prec = match precision {
Some(prec) => prec,
None => scale,
};
let len = chars.len();
let whole_len = len - scale;
let mut rep = ArrayString::new();
if append_sign && value.is_sign_negative() {
rep.push('-');
}
for i in 0..whole_len + prec {
if i == len - scale {
if i == 0 {
rep.push('0');
}
rep.push('.');
}
if i >= len {
rep.push('0');
} else {
let c = chars[len - i - 1];
rep.push(c);
}
}
if rep.is_empty() {
rep.push('0');
}
rep
}
pub(crate) fn fmt_scientific_notation(
value: &Decimal,
exponent_symbol: &str,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
#[cfg(not(feature = "std"))]
use alloc::string::ToString;
let mut exponent = -(value.scale() as isize);
let mut chars = Vec::new();
let mut working = value.mantissa_array3();
while !is_all_zero(&working) {
let remainder = div_by_u32(&mut working, 10u32);
chars.push(char::from(b'0' + remainder as u8));
}
let len = chars.len();
let mut rep;
if len > 1 {
if chars.iter().take(len - 1).all(|c| *c == '0') {
rep = chars.iter().skip(len - 1).collect::<String>();
} else {
chars.insert(len - 1, '.');
rep = chars.iter().rev().collect::<String>();
}
exponent += (len - 1) as isize;
} else {
rep = chars.iter().collect::<String>();
}
rep.push_str(exponent_symbol);
rep.push_str(&exponent.to_string());
f.pad_integral(value.is_sign_positive(), "", &rep)
}
pub(crate) fn parse_str_radix_10(str: &str) -> Result<Decimal, crate::Error> {
if str.is_empty() {
return Err(Error::from("Invalid decimal: empty"));
}
let mut offset = 0;
let mut len = str.len();
let bytes = str.as_bytes();
let mut negative = false;
if bytes[offset] == b'-' {
negative = true; offset += 1;
len -= 1;
} else if bytes[offset] == b'+' {
offset += 1;
len -= 1;
}
let mut digits_before_dot: i32 = -1; let mut coeff = ArrayVec::<[_; MAX_STR_BUFFER_SIZE]>::new();
let mut maybe_round = false;
while len > 0 {
let b = bytes[offset];
match b {
b'0'..=b'9' => {
coeff.push(u32::from(b - b'0'));
offset += 1;
len -= 1;
if coeff.len() as u32 > 28 {
maybe_round = true;
break;
}
}
b'.' => {
if digits_before_dot >= 0 {
return Err(Error::from("Invalid decimal: two decimal points"));
}
digits_before_dot = coeff.len() as i32;
offset += 1;
len -= 1;
}
b'_' => {
if coeff.is_empty() {
return Err(Error::from("Invalid decimal: must start lead with a number"));
}
offset += 1;
len -= 1;
}
_ => return Err(Error::from("Invalid decimal: unknown character")),
}
}
if maybe_round && offset < bytes.len() {
let next_byte = bytes[offset];
let digit = match next_byte {
b'0'..=b'9' => u32::from(next_byte - b'0'),
b'_' => 0,
b'.' => {
if digits_before_dot >= 0 {
return Err(Error::from("Invalid decimal: two decimal points"));
}
0
}
_ => return Err(Error::from("Invalid decimal: unknown character")),
};
if digit >= 5 {
let mut index = coeff.len() - 1;
loop {
let new_digit = coeff[index] + 1;
if new_digit <= 9 {
coeff[index] = new_digit;
break;
} else {
coeff[index] = 0;
if index == 0 {
coeff.insert(0, 1u32);
digits_before_dot += 1;
coeff.pop();
break;
}
}
index -= 1;
}
}
}
if coeff.is_empty() {
return Err(Error::from("Invalid decimal: no digits found"));
}
let mut scale = if digits_before_dot >= 0 {
(coeff.len() as u32) - (digits_before_dot as u32)
} else {
0
};
let mut data = [0u32, 0u32, 0u32];
let mut tmp = [0u32, 0u32, 0u32];
let len = coeff.len();
for (i, digit) in coeff.iter().enumerate() {
tmp[0] = data[0];
tmp[1] = data[1];
tmp[2] = data[2];
let overflow = mul_by_10(&mut tmp);
if overflow > 0 {
if (i as i32) < digits_before_dot && i + 1 < len {
return Err(Error::from("Invalid decimal: overflow from too many digits"));
}
if *digit >= 5 {
let carry = add_one_internal(&mut data);
if carry > 0 {
return Err(Error::from("Invalid decimal: overflow when rounding"));
}
}
let diff = (len - i) as u32;
if diff > scale {
return Err(Error::from("Invalid decimal: overflow from scale mismatch"));
}
scale -= diff;
break;
} else {
data[0] = tmp[0];
data[1] = tmp[1];
data[2] = tmp[2];
let carry = add_by_internal(&mut data, &[*digit]);
if carry > 0 {
return Err(Error::from("Invalid decimal: overflow from carry"));
}
}
}
Ok(Decimal::from_parts(data[0], data[1], data[2], negative, scale))
}
pub(crate) fn parse_str_radix_n(str: &str, radix: u32) -> Result<Decimal, crate::Error> {
if str.is_empty() {
return Err(Error::from("Invalid decimal: empty"));
}
if radix < 2 {
return Err(Error::from("Unsupported radix < 2"));
}
if radix > 36 {
return Err(Error::from("Unsupported radix > 36"));
}
let mut offset = 0;
let mut len = str.len();
let bytes = str.as_bytes();
let mut negative = false;
if bytes[offset] == b'-' {
negative = true; offset += 1;
len -= 1;
} else if bytes[offset] == b'+' {
offset += 1;
len -= 1;
}
let mut digits_before_dot: i32 = -1; let mut coeff = ArrayVec::<[_; 96]>::new();
let (max_n, max_alpha_lower, max_alpha_upper) = if radix <= 10 {
(b'0' + (radix - 1) as u8, 0, 0)
} else {
let adj = (radix - 11) as u8;
(b'9', adj + b'a', adj + b'A')
};
let estimated_max_precision = match radix {
2 => 96,
3 => 61,
4 => 48,
5 => 42,
6 => 38,
7 => 35,
8 => 32,
9 => 31,
10 => 28,
11 => 28,
12 => 27,
13 => 26,
14 => 26,
15 => 25,
16 => 24,
17 => 24,
18 => 24,
19 => 23,
20 => 23,
21 => 22,
22 => 22,
23 => 22,
24 => 21,
25 => 21,
26 => 21,
27 => 21,
28 => 20,
29 => 20,
30 => 20,
31 => 20,
32 => 20,
33 => 20,
34 => 19,
35 => 19,
36 => 19,
_ => return Err(Error::from("Unsupported radix")),
};
let mut maybe_round = false;
while len > 0 {
let b = bytes[offset];
match b {
b'0'..=b'9' => {
if b > max_n {
return Err(Error::from("Invalid decimal: invalid character"));
}
coeff.push(u32::from(b - b'0'));
offset += 1;
len -= 1;
if coeff.len() as u32 > estimated_max_precision {
maybe_round = true;
break;
}
}
b'a'..=b'z' => {
if b > max_alpha_lower {
return Err(Error::from("Invalid decimal: invalid character"));
}
coeff.push(u32::from(b - b'a') + 10);
offset += 1;
len -= 1;
if coeff.len() as u32 > estimated_max_precision {
maybe_round = true;
break;
}
}
b'A'..=b'Z' => {
if b > max_alpha_upper {
return Err(Error::from("Invalid decimal: invalid character"));
}
coeff.push(u32::from(b - b'A') + 10);
offset += 1;
len -= 1;
if coeff.len() as u32 > estimated_max_precision {
maybe_round = true;
break;
}
}
b'.' => {
if digits_before_dot >= 0 {
return Err(Error::from("Invalid decimal: two decimal points"));
}
digits_before_dot = coeff.len() as i32;
offset += 1;
len -= 1;
}
b'_' => {
if coeff.is_empty() {
return Err(Error::from("Invalid decimal: must start lead with a number"));
}
offset += 1;
len -= 1;
}
_ => return Err(Error::from("Invalid decimal: unknown character")),
}
}
if maybe_round && offset < bytes.len() {
let next_byte = bytes[offset];
let digit = match next_byte {
b'0'..=b'9' => {
if next_byte > max_n {
return Err(Error::from("Invalid decimal: invalid character"));
}
u32::from(next_byte - b'0')
}
b'a'..=b'z' => {
if next_byte > max_alpha_lower {
return Err(Error::from("Invalid decimal: invalid character"));
}
u32::from(next_byte - b'a') + 10
}
b'A'..=b'Z' => {
if next_byte > max_alpha_upper {
return Err(Error::from("Invalid decimal: invalid character"));
}
u32::from(next_byte - b'A') + 10
}
b'_' => 0,
b'.' => {
if digits_before_dot >= 0 {
return Err(Error::from("Invalid decimal: two decimal points"));
}
0
}
_ => return Err(Error::from("Invalid decimal: unknown character")),
};
let midpoint = if radix & 0x1 == 1 { radix / 2 } else { (radix + 1) / 2 };
if digit >= midpoint {
let mut index = coeff.len() - 1;
loop {
let new_digit = coeff[index] + 1;
if new_digit <= 9 {
coeff[index] = new_digit;
break;
} else {
coeff[index] = 0;
if index == 0 {
coeff.insert(0, 1u32);
digits_before_dot += 1;
coeff.pop();
break;
}
}
index -= 1;
}
}
}
if coeff.is_empty() {
return Err(Error::from("Invalid decimal: no digits found"));
}
let mut scale = if digits_before_dot >= 0 {
(coeff.len() as u32) - (digits_before_dot as u32)
} else {
0
};
let mut data = [0u32, 0u32, 0u32];
let mut tmp = [0u32, 0u32, 0u32];
let len = coeff.len();
for (i, digit) in coeff.iter().enumerate() {
tmp[0] = data[0];
tmp[1] = data[1];
tmp[2] = data[2];
let overflow = mul_by_u32(&mut tmp, radix);
if overflow > 0 {
if (i as i32) < digits_before_dot && i + 1 < len {
return Err(Error::from("Invalid decimal: overflow from too many digits"));
}
if *digit >= 5 {
let carry = add_one_internal(&mut data);
if carry > 0 {
return Err(Error::from("Invalid decimal: overflow when rounding"));
}
}
let diff = (len - i) as u32;
if diff > scale {
return Err(Error::from("Invalid decimal: overflow from scale mismatch"));
}
scale -= diff;
break;
} else {
data[0] = tmp[0];
data[1] = tmp[1];
data[2] = tmp[2];
let carry = add_by_internal(&mut data, &[*digit]);
if carry > 0 {
return Err(Error::from("Invalid decimal: overflow from carry"));
}
}
}
Ok(Decimal::from_parts(data[0], data[1], data[2], negative, scale))
}