1use std::fmt;
8use std::str::FromStr;
9
10use arrow::datatypes::DECIMAL128_MAX_PRECISION;
11use arrow_buffer::i256;
12
13pub const MAX_DECIMAL_PRECISION: u8 = DECIMAL128_MAX_PRECISION;
15const POW10_BASE: i256 = i256::from_i128(10);
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum DecimalError {
20 ScaleOutOfRange { scale: i8 },
22 PrecisionOverflow { value: i128, scale: i8 },
24 Overflow,
26 DivisionByZero,
28 InexactRescale { from: i8, to: i8 },
30}
31
32impl fmt::Display for DecimalError {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 DecimalError::ScaleOutOfRange { scale } => {
36 write!(f, "decimal scale {scale} outside supported range")
37 }
38 DecimalError::PrecisionOverflow { value, scale } => {
39 write!(
40 f,
41 "decimal value {value} with scale {scale} exceeds maximum precision"
42 )
43 }
44 DecimalError::Overflow => write!(f, "decimal arithmetic overflow"),
45 DecimalError::DivisionByZero => write!(f, "decimal division by zero"),
46 DecimalError::InexactRescale { from, to } => {
47 write!(
48 f,
49 "cannot rescale decimal from scale {from} to {to} without losing precision"
50 )
51 }
52 }
53 }
54}
55
56impl std::error::Error for DecimalError {}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
60pub struct DecimalValue {
61 value: i128,
62 scale: i8,
63}
64
65impl DecimalValue {
66 pub fn new(value: i128, scale: i8) -> Result<Self, DecimalError> {
68 if !scale_within_bounds(scale as i16) {
69 return Err(DecimalError::ScaleOutOfRange { scale });
70 }
71 let precision = digit_count_i256(i256::from_i128(value));
72 if precision > MAX_DECIMAL_PRECISION {
73 return Err(DecimalError::PrecisionOverflow { value, scale });
74 }
75 Ok(Self { value, scale })
76 }
77
78 pub fn from_i64(value: i64) -> Self {
80 Self::new(value as i128, 0).expect("i64 fits within Decimal128 limits")
81 }
82
83 #[inline]
85 pub fn raw_value(self) -> i128 {
86 self.value
87 }
88
89 #[inline]
91 pub fn scale(self) -> i8 {
92 self.scale
93 }
94
95 #[inline]
97 pub fn precision(self) -> u8 {
98 digit_count_i256(i256::from_i128(self.value))
99 }
100
101 pub fn to_f64(self) -> f64 {
103 if self.value == 0 {
104 return 0.0;
105 }
106 let denominator = 10_f64.powi(self.scale as i32);
107 (self.value as f64) / denominator
108 }
109}
110
111impl fmt::Display for DecimalValue {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 if self.scale == 0 {
114 return write!(f, "{}", self.value);
115 }
116 let negative = self.value < 0;
117 let digits = digit_buffer(i256::from_i128(self.value));
118 if digits.len() <= self.scale as usize {
119 let mut result = String::with_capacity(self.scale as usize + 2);
120 if negative {
121 result.push('-');
122 }
123 result.push('0');
124 result.push('.');
125 for _ in digits.len()..self.scale as usize {
126 result.push('0');
127 }
128 result.push_str(&digits);
129 return f.write_str(&result);
130 }
131 let split = digits.len() - self.scale as usize;
132 if negative {
133 f.write_str("-")?;
134 }
135 f.write_str(&digits[..split])?;
136 f.write_str(".")?;
137 f.write_str(&digits[split..])
138 }
139}
140
141impl FromStr for DecimalValue {
142 type Err = DecimalError;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 let s = s.trim();
146 let (int_part, frac_part) = match s.split_once('.') {
147 Some((i, f)) => (i, f),
148 None => (s, ""),
149 };
150
151 let scale = frac_part.len();
152 if scale > MAX_DECIMAL_PRECISION as usize {
153 return Err(DecimalError::ScaleOutOfRange { scale: scale as i8 });
154 }
155
156 let combined = format!("{}{}", int_part, frac_part);
157 let value = combined
158 .parse::<i128>()
159 .map_err(|_| DecimalError::Overflow)?;
160
161 Self::new(value, scale as i8)
162 }
163}
164
165impl PartialOrd for DecimalValue {
166 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
167 Some(self.cmp(other))
168 }
169}
170
171impl Ord for DecimalValue {
172 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
173 if self.scale == other.scale {
174 return self.value.cmp(&other.value);
175 }
176
177 let max_scale = std::cmp::max(self.scale, other.scale);
178 let scale_diff_self = (max_scale - self.scale) as u32;
179 let scale_diff_other = (max_scale - other.scale) as u32;
180
181 let l_i256 = i256::from_i128(self.value);
182 let r_i256 = i256::from_i128(other.value);
183
184 let l_scaled = l_i256.wrapping_mul(POW10_BASE.wrapping_pow(scale_diff_self));
187 let r_scaled = r_i256.wrapping_mul(POW10_BASE.wrapping_pow(scale_diff_other));
188
189 l_scaled.cmp(&r_scaled)
190 }
191}
192
193fn digit_count_i256(mut value: i256) -> u8 {
194 if value == i256::ZERO {
195 return 1;
196 }
197 if value < i256::ZERO {
198 value = value.wrapping_neg();
199 }
200 let mut count: u8 = 0;
201 while value != i256::ZERO {
202 value = value.wrapping_div(POW10_BASE);
203 count += 1;
204 }
205 count
206}
207
208fn digit_buffer(mut value: i256) -> String {
209 if value == i256::ZERO {
210 return "0".to_owned();
211 }
212 if value < i256::ZERO {
213 value = value.wrapping_neg();
214 }
215 let mut buf = Vec::new();
216 let ten = POW10_BASE;
217 let mut current = value;
218 while current != i256::ZERO {
219 let rem = current.wrapping_rem(ten);
220 let digit = rem
221 .to_i128()
222 .expect("remainder from decimal division fits in i128") as i32;
223 buf.push((b'0' + digit as u8) as char);
224 current = current.wrapping_div(ten);
225 }
226 buf.iter().rev().collect()
227}
228
229pub fn scale_within_bounds(scale: i16) -> bool {
230 let max = MAX_DECIMAL_PRECISION as i16;
231 (-max..=max).contains(&scale)
232}