1use crate::arithmetic::{pow10, POW10};
16use crate::decimal::{Decimal, MAX_SCALE};
17use crate::error::ArithmeticError;
18
19impl Decimal {
22 #[inline]
24 pub fn from_u64(v: u64) -> Self {
25 Decimal::new_unchecked(v as i128, 0)
26 }
27
28 #[inline]
30 pub fn from_i64(v: i64) -> Self {
31 Decimal::new_unchecked(v as i128, 0)
32 }
33
34 pub fn from_u128(v: u128) -> Result<Self, ArithmeticError> {
38 let mantissa = i128::try_from(v).map_err(|_| ArithmeticError::Overflow)?;
39 Decimal::new(mantissa, 0)
40 }
41
42 #[inline]
44 pub fn from_i128(v: i128) -> Self {
45 Decimal::new_unchecked(v, 0)
46 }
47
48 pub fn from_token_amount(amount: u64, decimals: u8) -> Result<Self, ArithmeticError> {
52 Decimal::new(amount as i128, decimals)
53 }
54}
55
56impl Decimal {
59 pub fn to_i128_truncated(self) -> i128 {
63 if self.scale == 0 {
64 return self.mantissa;
65 }
66 let factor = POW10[self.scale as usize];
67 self.mantissa / factor
68 }
69
70 pub fn to_u64_truncated(self) -> Result<u64, ArithmeticError> {
74 let v = self.to_i128_truncated();
75 u64::try_from(v).map_err(|_| ArithmeticError::Overflow)
76 }
77
78 pub fn to_token_amount(
82 self,
83 decimals: u8,
84 mode: crate::rounding::RoundingMode,
85 ) -> Result<u64, ArithmeticError> {
86 let rounded = self.round(decimals, mode)?;
87 let diff = decimals.saturating_sub(rounded.scale());
88 let factor = pow10(diff)?;
89 let raw = rounded
90 .mantissa()
91 .checked_mul(factor)
92 .ok_or(ArithmeticError::Overflow)?;
93 u64::try_from(raw).map_err(|_| ArithmeticError::Overflow)
94 }
95}
96
97impl Decimal {
100 pub fn from_str_exact(s: &str) -> Result<Self, ArithmeticError> {
111 let s = s.trim();
112 if s.is_empty() {
113 return Err(ArithmeticError::InvalidInput);
114 }
115
116 let (negative, rest) = if s.starts_with('-') {
117 (true, &s[1..])
118 } else if s.starts_with('+') {
119 (false, &s[1..])
120 } else {
121 (false, s)
122 };
123
124 if rest.is_empty() {
125 return Err(ArithmeticError::InvalidInput);
126 }
127
128 let (int_part, frac_part, scale) = if let Some(dot) = rest.find('.') {
129 let frac = &rest[dot + 1..];
130 if frac.len() > MAX_SCALE as usize {
131 return Err(ArithmeticError::ScaleExceeded);
132 }
133 (&rest[..dot], frac, frac.len() as u8)
134 } else {
135 (rest, "", 0u8)
136 };
137
138 if int_part.is_empty() && frac_part.is_empty() {
139 return Err(ArithmeticError::InvalidInput);
140 }
141
142 let mut mantissa: i128 = 0;
143
144 for ch in int_part.bytes() {
145 let digit = ch.wrapping_sub(b'0');
146 if digit > 9 {
147 return Err(ArithmeticError::InvalidInput);
148 }
149 mantissa = mantissa
150 .checked_mul(10)
151 .and_then(|m| m.checked_add(digit as i128))
152 .ok_or(ArithmeticError::Overflow)?;
153 }
154
155 for ch in frac_part.bytes() {
156 let digit = ch.wrapping_sub(b'0');
157 if digit > 9 {
158 return Err(ArithmeticError::InvalidInput);
159 }
160 mantissa = mantissa
161 .checked_mul(10)
162 .and_then(|m| m.checked_add(digit as i128))
163 .ok_or(ArithmeticError::Overflow)?;
164 }
165
166 if negative {
167 mantissa = mantissa.checked_neg().ok_or(ArithmeticError::Overflow)?;
168 }
169
170 Decimal::new(mantissa, scale)
171 }
172}
173
174#[cfg(test)]
177mod tests {
178 use super::*;
179
180 fn d(m: i128, s: u8) -> Decimal {
181 Decimal::new(m, s).unwrap()
182 }
183
184 #[test]
185 fn from_u64_basic() {
186 let x = Decimal::from_u64(1_000_000);
187 assert_eq!(x.mantissa(), 1_000_000);
188 assert_eq!(x.scale(), 0);
189 }
190
191 #[test]
192 fn from_i64_negative() {
193 let x = Decimal::from_i64(-42);
194 assert_eq!(x.mantissa(), -42);
195 assert_eq!(x.scale(), 0);
196 }
197
198 #[test]
199 fn from_u128_fits() {
200 assert!(Decimal::from_u128(u128::from(u64::MAX)).is_ok());
201 }
202
203 #[test]
204 fn from_u128_overflow() {
205 assert!(Decimal::from_u128((i128::MAX as u128) + 1).is_err());
206 }
207
208 #[test]
209 fn from_token_amount() {
210 let x = Decimal::from_token_amount(1_500_000, 6).unwrap();
211 assert_eq!(x.mantissa(), 1_500_000);
212 assert_eq!(x.scale(), 6);
213 }
214
215 #[test]
216 fn to_i128_truncated_no_scale() {
217 assert_eq!(d(42, 0).to_i128_truncated(), 42);
218 }
219
220 #[test]
221 fn to_i128_truncated_rounds_toward_zero() {
222 assert_eq!(d(157, 2).to_i128_truncated(), 1);
223 assert_eq!(d(-157, 2).to_i128_truncated(), -1);
224 }
225
226 #[test]
227 fn to_u64_truncated_positive() {
228 assert_eq!(d(157, 2).to_u64_truncated().unwrap(), 1u64);
229 }
230
231 #[test]
232 fn to_u64_truncated_negative_fails() {
233 assert!(d(-1, 0).to_u64_truncated().is_err());
234 }
235
236 #[test]
237 fn parse_integer() {
238 assert_eq!(Decimal::from_str_exact("42").unwrap(), d(42, 0));
239 }
240
241 #[test]
242 fn parse_decimal_two_places() {
243 assert_eq!(Decimal::from_str_exact("1.23").unwrap(), d(123, 2));
244 }
245
246 #[test]
247 fn parse_negative_decimal() {
248 assert_eq!(Decimal::from_str_exact("-1.23").unwrap(), d(-123, 2));
249 }
250
251 #[test]
252 fn parse_positive_sign() {
253 assert_eq!(Decimal::from_str_exact("+1.23").unwrap(), d(123, 2));
254 }
255
256 #[test]
257 fn parse_zero_int_part() {
258 assert_eq!(Decimal::from_str_exact("0.001").unwrap(), d(1, 3));
259 }
260
261 #[test]
262 fn parse_trailing_zeros_set_scale() {
263 assert_eq!(Decimal::from_str_exact("1.00").unwrap().scale(), 2);
264 }
265
266 #[test]
267 fn parse_empty_fails() {
268 assert!(Decimal::from_str_exact("").is_err());
269 }
270
271 #[test]
272 fn parse_alpha_fails() {
273 assert!(Decimal::from_str_exact("abc").is_err());
274 }
275
276 #[test]
277 fn parse_too_many_decimals_fails() {
278 let s = "0.00000000000000000000000000001"; assert!(matches!(
280 Decimal::from_str_exact(s),
281 Err(ArithmeticError::ScaleExceeded)
282 ));
283 }
284
285 #[test]
286 fn roundtrip_token_amount() {
287 use crate::rounding::RoundingMode;
288 let x = Decimal::from_token_amount(1_234_567, 6).unwrap();
289 let back = x.to_token_amount(6, RoundingMode::HalfEven).unwrap();
290 assert_eq!(back, 1_234_567u64);
291 }
292}