Skip to main content

linkrs_core/value/
decimal128.rs

1//! Decimal128 type implementation
2//!
3//! This module implements the 128-bit decimal floating point type of the IEEE 754-2008 standard.
4//!
5//! ## Characteristics ##
6//!
7//! - 34-bit decimal precision
8//! - Avoiding Precision Problems with Binary Floating Point Numbers
9//! - Ideal for financial computing and scientific computing
10//! - Compatible with MongoDB BSON Decimal128
11//!
12//! ## Usage scenarios
13//!
14//! - Financial applications (monetary calculations, interest rate calculations)
15//! - Scientific computing (numerical calculations requiring high precision)
16//! - Tax calculations (need to be precise to the nearest cent)
17//! - Accounting systems (to avoid rounding errors)
18//!
19//! ## Performance considerations
20//!
21//! Decimal128 operations are slower than native floating-point numbers, but provide accurate decimal calculations.
22//! For scenarios where high precision is not required, the Float type is recommended.
23
24use dec::Decimal128;
25use serde::{Deserialize, Serialize};
26use std::fmt;
27use std::str::FromStr;
28
29/// Decimal128 Value Wrapper
30///
31/// Wraps the `dec::Decimal128` type to provide integration with the Linkrs type system.
32///
33/// ## Example
34///
35/// ```rust
36/// use linkrs_core::value::decimal128::Decimal128Value;
37///
38/// let d1 = "123.456".parse::<Decimal128Value>().expect("parse failed");
39/// let d2 = "789.012".parse::<Decimal128Value>().expect("parse failed");
40/// let sum = &d1 + &d2;
41/// assert_eq!(sum.to_string(), "912.468");
42/// ```
43#[derive(Debug, Clone)]
44pub struct Decimal128Value {
45    inner: Decimal128,
46}
47
48impl Serialize for Decimal128Value {
49    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50    where
51        S: serde::Serializer,
52    {
53        self.to_string().serialize(serializer)
54    }
55}
56
57impl<'de> Deserialize<'de> for Decimal128Value {
58    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59    where
60        D: serde::Deserializer<'de>,
61    {
62        let s = String::deserialize(deserializer)?;
63        Self::from_str(&s).map_err(serde::de::Error::custom)
64    }
65}
66
67impl Decimal128Value {
68    /// Create a new Decimal128 value
69    pub fn new(inner: Decimal128) -> Self {
70        Self { inner }
71    }
72
73    /// Creating Decimal128 from i64
74    pub fn from_i64(n: i64) -> Self {
75        Self {
76            inner: Decimal128::from(n),
77        }
78    }
79
80    /// Creating Decimal128 from u64
81    pub fn from_u64(n: u64) -> Self {
82        Self {
83            inner: Decimal128::from(n),
84        }
85    }
86
87    /// Create Decimal128 from f64 (note: there may be a loss of precision)
88    pub fn from_f64(n: f64) -> Option<Self> {
89        let s = n.to_string();
90        Self::from_str(&s).ok()
91    }
92
93    /// Get the internal Decimal128 value
94    pub fn inner(&self) -> &Decimal128 {
95        &self.inner
96    }
97
98    /// Get internal Decimal128 value (variable)
99    pub fn inner_mut(&mut self) -> &mut Decimal128 {
100        &mut self.inner
101    }
102
103    /// addition
104    pub fn add(&self, other: &Self) -> Result<Self, String> {
105        Ok(Self {
106            inner: self.inner + other.inner,
107        })
108    }
109
110    /// subtraction operation
111    pub fn sub(&self, other: &Self) -> Result<Self, String> {
112        Ok(Self {
113            inner: self.inner - other.inner,
114        })
115    }
116
117    /// multiplication
118    pub fn mul(&self, other: &Self) -> Result<Self, String> {
119        Ok(Self {
120            inner: self.inner * other.inner,
121        })
122    }
123
124    /// division
125    pub fn div(&self, other: &Self) -> Result<Self, String> {
126        if other.inner == Decimal128::ZERO {
127            return Err("division error".to_string());
128        }
129        Ok(Self {
130            inner: self.inner / other.inner,
131        })
132    }
133
134    /// modulo operation (math.)
135    pub fn rem(&self, other: &Self) -> Result<Self, String> {
136        if other.inner == Decimal128::ZERO {
137            return Err("division error".to_string());
138        }
139        Ok(Self {
140            inner: self.inner % other.inner,
141        })
142    }
143
144    /// absolute value
145    pub fn abs(&self) -> Self {
146        Self {
147            inner: if self.inner < Decimal128::ZERO {
148                -self.inner
149            } else {
150                self.inner
151            },
152        }
153    }
154
155    /// retrieve the opposite of what one intended
156    pub fn neg(&self) -> Self {
157        Self { inner: -self.inner }
158    }
159
160    /// Rounding to specified decimal places
161    pub fn round_dp(&self, dp: u32) -> Self {
162        let s = self.to_string();
163        if let Some(dot_pos) = s.find('.') {
164            let integer_part = &s[..dot_pos];
165            let fractional_part = &s[dot_pos + 1..];
166            if fractional_part.len() <= dp as usize {
167                return self.clone();
168            }
169            let rounded_fractional = &fractional_part[..dp as usize];
170            let rounded_str = format!("{}.{}", integer_part, rounded_fractional);
171            Self::from_str(&rounded_str).unwrap_or_else(|_| self.clone())
172        } else {
173            self.clone()
174        }
175    }
176
177    /// round down
178    pub fn floor(&self) -> Self {
179        let s = self.to_string();
180        if let Some(dot_pos) = s.find('.') {
181            let integer_part = &s[..dot_pos];
182            Self::from_str(integer_part).unwrap_or_else(|_| self.clone())
183        } else {
184            self.clone()
185        }
186    }
187
188    /// Round up
189    pub fn ceil(&self) -> Self {
190        let s = self.to_string();
191        if let Some(dot_pos) = s.find('.') {
192            let fractional_part = &s[dot_pos + 1..];
193            if fractional_part.chars().all(|c| c == '0') {
194                self.clone()
195            } else {
196                let integer_part = &s[..dot_pos];
197                let int_value: i64 = integer_part.parse().unwrap_or(0);
198                let ceil_value = if self.inner >= Decimal128::ZERO {
199                    int_value + 1
200                } else {
201                    int_value
202                };
203                Self::from_i64(ceil_value)
204            }
205        } else {
206            self.clone()
207        }
208    }
209
210    /// Is it zero?
211    pub fn is_zero(&self) -> bool {
212        self.inner == Decimal128::ZERO
213    }
214
215    /// Whether the number is negative or not
216    pub fn is_negative(&self) -> bool {
217        self.inner < Decimal128::ZERO
218    }
219
220    /// Positive or not
221    pub fn is_positive(&self) -> bool {
222        self.inner > Decimal128::ZERO
223    }
224
225    /// Whether NaN
226    pub fn is_nan(&self) -> bool {
227        self.inner == Decimal128::NAN
228    }
229}
230
231impl Default for Decimal128Value {
232    fn default() -> Self {
233        Self {
234            inner: Decimal128::ZERO,
235        }
236    }
237}
238
239impl fmt::Display for Decimal128Value {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        write!(f, "{}", self.inner)
242    }
243}
244
245impl FromStr for Decimal128Value {
246    type Err = String;
247
248    fn from_str(s: &str) -> Result<Self, Self::Err> {
249        Decimal128::from_str(s)
250            .map(|inner| Self { inner })
251            .map_err(|e| format!("Decimal128 parse failed: {}", e))
252    }
253}
254
255impl std::ops::Add for &Decimal128Value {
256    type Output = Decimal128Value;
257
258    fn add(self, other: Self) -> Self::Output {
259        Decimal128Value {
260            inner: self.inner + other.inner,
261        }
262    }
263}
264
265impl std::ops::Sub for &Decimal128Value {
266    type Output = Decimal128Value;
267
268    fn sub(self, other: Self) -> Self::Output {
269        Decimal128Value {
270            inner: self.inner - other.inner,
271        }
272    }
273}
274
275impl std::ops::Mul for &Decimal128Value {
276    type Output = Decimal128Value;
277
278    fn mul(self, other: Self) -> Self::Output {
279        Decimal128Value {
280            inner: self.inner * other.inner,
281        }
282    }
283}
284
285impl std::ops::Div for &Decimal128Value {
286    type Output = Decimal128Value;
287
288    fn div(self, other: Self) -> Self::Output {
289        Decimal128Value {
290            inner: self.inner / other.inner,
291        }
292    }
293}
294
295impl std::ops::Rem for &Decimal128Value {
296    type Output = Decimal128Value;
297
298    fn rem(self, other: Self) -> Self::Output {
299        Decimal128Value {
300            inner: self.inner % other.inner,
301        }
302    }
303}
304
305impl std::ops::Neg for Decimal128Value {
306    type Output = Decimal128Value;
307
308    fn neg(self) -> Self::Output {
309        Decimal128Value { inner: -self.inner }
310    }
311}
312
313impl PartialEq for Decimal128Value {
314    fn eq(&self, other: &Self) -> bool {
315        self.inner == other.inner
316    }
317}
318
319impl Eq for Decimal128Value {}
320
321impl PartialOrd for Decimal128Value {
322    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
323        Some(self.cmp(other))
324    }
325}
326
327impl Ord for Decimal128Value {
328    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
329        if self.inner < other.inner {
330            std::cmp::Ordering::Less
331        } else if self.inner > other.inner {
332            std::cmp::Ordering::Greater
333        } else {
334            std::cmp::Ordering::Equal
335        }
336    }
337}
338
339impl std::hash::Hash for Decimal128Value {
340    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
341        self.to_string().hash(state);
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn test_from_str() {
351        let d = "123.456".parse::<Decimal128Value>().expect("parse failed");
352        assert_eq!(d.to_string(), "123.456");
353    }
354
355    #[test]
356    fn test_from_i64() {
357        let d = Decimal128Value::from_i64(123456);
358        assert_eq!(d.to_string(), "123456");
359    }
360
361    #[test]
362    fn test_add() {
363        let d1 = "123.456".parse::<Decimal128Value>().expect("parse failed");
364        let d2 = "789.012".parse::<Decimal128Value>().expect("parse failed");
365        let sum = &d1 + &d2;
366        assert_eq!(sum.to_string(), "912.468");
367    }
368
369    #[test]
370    fn test_sub() {
371        let d1 = "789.012".parse::<Decimal128Value>().expect("parse failed");
372        let d2 = "123.456".parse::<Decimal128Value>().expect("parse failed");
373        let diff = &d1 - &d2;
374        assert_eq!(diff.to_string(), "665.556");
375    }
376
377    #[test]
378    fn test_mul() {
379        let d1 = "12.34".parse::<Decimal128Value>().expect("parse failed");
380        let d2 = "5.6".parse::<Decimal128Value>().expect("parse failed");
381        let product = &d1 * &d2;
382        assert_eq!(product.to_string(), "69.104");
383    }
384
385    #[test]
386    fn test_div() {
387        let d1 = "100.0".parse::<Decimal128Value>().expect("parse failed");
388        let d2 = "4.0".parse::<Decimal128Value>().expect("parse failed");
389        let quotient = &d1 / &d2;
390        assert_eq!(quotient.to_string(), "25");
391    }
392
393    #[test]
394    fn test_div_by_zero() {
395        let d1 = "100.0".parse::<Decimal128Value>().expect("parse failed");
396        let d2 = "0.0".parse::<Decimal128Value>().expect("parse failed");
397        let result = d1.div(&d2);
398        assert!(result.is_err());
399        assert_eq!(result.unwrap_err(), "division error");
400    }
401
402    #[test]
403    fn test_neg() {
404        let d = "123.456".parse::<Decimal128Value>().expect("parse failed");
405        let neg = -d.clone();
406        assert_eq!(neg.to_string(), "-123.456");
407    }
408
409    #[test]
410    fn test_abs() {
411        let d1 = "-123.456".parse::<Decimal128Value>().expect("parse failed");
412        let d2 = "123.456".parse::<Decimal128Value>().expect("parse failed");
413        assert_eq!(d1.abs().to_string(), "123.456");
414        assert_eq!(d2.abs().to_string(), "123.456");
415    }
416
417    #[test]
418    fn test_round_dp() {
419        let d = "123.456789"
420            .parse::<Decimal128Value>()
421            .expect("parse failed");
422        let rounded = d.round_dp(2);
423        assert_eq!(rounded.to_string(), "123.45");
424    }
425
426    #[test]
427    fn test_floor() {
428        let d = "123.789".parse::<Decimal128Value>().expect("parse failed");
429        let floored = d.floor();
430        assert_eq!(floored.to_string(), "123");
431    }
432
433    #[test]
434    fn test_ceil() {
435        let d = "123.789".parse::<Decimal128Value>().expect("parse failed");
436        let ceiled = d.ceil();
437        assert_eq!(ceiled.to_string(), "124");
438    }
439
440    #[test]
441    fn test_is_zero() {
442        let d = "0.0".parse::<Decimal128Value>().expect("parse failed");
443        assert!(d.is_zero());
444    }
445
446    #[test]
447    fn test_is_negative() {
448        let d = "-123.456".parse::<Decimal128Value>().expect("parse failed");
449        assert!(d.is_negative());
450    }
451
452    #[test]
453    fn test_is_positive() {
454        let d = "123.456".parse::<Decimal128Value>().expect("parse failed");
455        assert!(d.is_positive());
456    }
457
458    #[test]
459    fn test_finance_calculation() {
460        let price = "19.99".parse::<Decimal128Value>().expect("parse failed");
461        let quantity = "3".parse::<Decimal128Value>().expect("parse failed");
462        let tax_rate = "0.08".parse::<Decimal128Value>().expect("parse failed");
463
464        let subtotal = &price * &quantity;
465        let tax = &subtotal * &tax_rate;
466        let total = &subtotal + &tax;
467
468        assert_eq!(subtotal.to_string(), "59.97");
469        assert_eq!(tax.to_string(), "4.7976");
470        assert_eq!(total.to_string(), "64.7676");
471    }
472}