icydb_schema/decimal/
text.rs1use crate::decimal::{
4 DECIMAL_DIGIT_BUFFER_LEN, Decimal, MAX_SUPPORTED_SCALE, ParseDecimalError,
5 ParseDecimalErrorReason,
6};
7use std::{
8 fmt::{Arguments, Display, Formatter},
9 io::Write,
10 str::FromStr,
11};
12
13impl Decimal {
14 pub(in crate::decimal) fn from_float_text(args: Arguments<'_>) -> Option<Self> {
20 const CAPACITY: usize = 1 + DECIMAL_DIGIT_BUFFER_LEN + 1 + MAX_SUPPORTED_SCALE as usize;
21 let mut bytes = [0; CAPACITY];
22 let mut remaining = bytes.as_mut_slice();
23 remaining.write_fmt(args).ok()?;
24 let len = CAPACITY - remaining.len();
25 Self::from_str(std::str::from_utf8(&bytes[..len]).ok()?).ok()
26 }
27}
28
29impl Display for Decimal {
30 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31 let (mantissa, scale) = self.normalized_parts();
32
33 if mantissa == 0 {
34 return f.write_str("0");
35 }
36
37 let negative = mantissa.is_negative();
38 let mut digits = mantissa.unsigned_abs().to_string();
39
40 if scale == 0 {
41 if negative {
42 return write!(f, "-{digits}");
43 }
44
45 return f.write_str(&digits);
46 }
47
48 let scale_usize = usize::try_from(scale).map_err(|_| std::fmt::Error)?;
49
50 if digits.len() <= scale_usize {
51 let zeros = "0".repeat(scale_usize - digits.len());
52 let body = format!("0.{zeros}{digits}");
53 if negative {
54 write!(f, "-{body}")
55 } else {
56 f.write_str(&body)
57 }
58 } else {
59 let split = digits.len() - scale_usize;
60 let frac = digits.split_off(split);
61 if negative {
62 write!(f, "-{digits}.{frac}")
63 } else {
64 write!(f, "{digits}.{frac}")
65 }
66 }
67 }
68}
69
70impl FromStr for Decimal {
71 type Err = ParseDecimalError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 let input = s.trim();
76 if input.is_empty() {
77 return Err(ParseDecimalError::new(ParseDecimalErrorReason::Empty));
78 }
79
80 let (negative, unsigned) = if let Some(rest) = input.strip_prefix('-') {
81 (true, rest)
82 } else if let Some(rest) = input.strip_prefix('+') {
83 (false, rest)
84 } else {
85 (false, input)
86 };
87
88 if unsigned.contains(['e', 'E']) {
91 return Err(ParseDecimalError::new(
92 ParseDecimalErrorReason::ExponentNotationUnsupported,
93 ));
94 }
95
96 let (int_digits, frac_digits) = split_decimal_significand(unsigned)?;
98 let scale_i64 = i64::try_from(frac_digits.len()).map_err(|_| {
99 ParseDecimalError::new(ParseDecimalErrorReason::FractionalLengthOverflow)
100 })?;
101
102 let scale = u32::try_from(scale_i64)
103 .map_err(|_| ParseDecimalError::new(ParseDecimalErrorReason::ScaleOverflow))?;
104
105 let mantissa = int_digits
109 .bytes()
110 .chain(frac_digits.bytes())
111 .try_fold(0i128, |mantissa, digit| {
112 let mantissa = mantissa.checked_mul(10)?;
113 let digit = i128::from(digit - b'0');
114 if negative {
115 mantissa.checked_sub(digit)
116 } else {
117 mantissa.checked_add(digit)
118 }
119 })
120 .ok_or_else(|| ParseDecimalError::new(ParseDecimalErrorReason::MantissaOverflow))?;
121
122 Self::checked_from_mantissa_scale(mantissa, scale).ok_or_else(|| {
123 ParseDecimalError::new(ParseDecimalErrorReason::ScaleExceedsSupportedRange)
124 })
125 }
126}
127
128fn split_decimal_significand(input: &str) -> Result<(&str, &str), ParseDecimalError> {
129 let mut segments = input.split('.');
130 let int_digits = segments
131 .next()
132 .ok_or_else(|| ParseDecimalError::new(ParseDecimalErrorReason::InvalidSignificand))?;
133 let frac_digits = segments.next().unwrap_or("");
134
135 if segments.next().is_some() {
136 return Err(ParseDecimalError::new(
137 ParseDecimalErrorReason::InvalidSignificand,
138 ));
139 }
140
141 if int_digits.is_empty() && frac_digits.is_empty() {
142 return Err(ParseDecimalError::new(
143 ParseDecimalErrorReason::InvalidSignificand,
144 ));
145 }
146
147 if !int_digits.chars().all(|c| c.is_ascii_digit()) {
148 return Err(ParseDecimalError::new(
149 ParseDecimalErrorReason::InvalidDigits,
150 ));
151 }
152
153 if !frac_digits.chars().all(|c| c.is_ascii_digit()) {
154 return Err(ParseDecimalError::new(
155 ParseDecimalErrorReason::InvalidDigits,
156 ));
157 }
158
159 Ok((int_digits, frac_digits))
160}