Skip to main content

fermat_core/
decimal.rs

1//! Core `Decimal` type definition and constants.
2
3use crate::error::ArithmeticError;
4
5/// Maximum allowed scale (decimal places). Fixed at 28 to stay within `i128` range.
6///
7/// At scale 28 the mantissa unit is `10^-28`, giving sub-yocto precision.
8pub const MAX_SCALE: u8 = 28;
9
10/// Conventional scale for USDC (6 decimal places).
11pub const USDC_SCALE: u8 = 6;
12
13/// Conventional scale for SOL lamports (9 decimal places).
14pub const SOL_SCALE: u8 = 9;
15
16/// Conventional scale for percentage values expressed as basis points (4 d.p.).
17pub const BPS_SCALE: u8 = 4;
18
19/// A 128-bit signed fixed-point decimal.
20///
21/// ```text
22/// value = mantissa × 10^(-scale)
23/// ```
24///
25/// - `mantissa`: signed coefficient stored as `i128`
26/// - `scale`: number of decimal places in `[0, MAX_SCALE]`
27///
28/// On-chain Borsh encoding: 17 bytes (16-byte LE mantissa + 1-byte scale).
29#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
30pub struct Decimal {
31    pub(crate) mantissa: i128,
32    pub(crate) scale: u8,
33}
34
35impl Decimal {
36    /// Additive identity: `0 × 10^0`.
37    pub const ZERO: Self = Self {
38        mantissa: 0,
39        scale: 0,
40    };
41
42    /// Multiplicative identity: `1 × 10^0`.
43    pub const ONE: Self = Self {
44        mantissa: 1,
45        scale: 0,
46    };
47
48    /// Maximum representable value: `i128::MAX × 10^0`.
49    pub const MAX: Self = Self {
50        mantissa: i128::MAX,
51        scale: 0,
52    };
53
54    /// Minimum representable value: `i128::MIN × 10^0`.
55    pub const MIN: Self = Self {
56        mantissa: i128::MIN,
57        scale: 0,
58    };
59
60    /// Negative one: `-1 × 10^0`.
61    pub const NEG_ONE: Self = Self {
62        mantissa: -1,
63        scale: 0,
64    };
65
66    /// `100 × 10^0` — useful for percentage arithmetic.
67    pub const HUNDRED: Self = Self {
68        mantissa: 100,
69        scale: 0,
70    };
71
72    /// `10_000 × 10^0` — basis-points denominator.
73    pub const TEN_THOUSAND: Self = Self {
74        mantissa: 10_000,
75        scale: 0,
76    };
77
78    /// Construct a `Decimal` from a raw mantissa and scale.
79    ///
80    /// Returns `Err(ArithmeticError::ScaleExceeded)` if `scale > MAX_SCALE`.
81    #[inline]
82    pub fn new(mantissa: i128, scale: u8) -> Result<Self, ArithmeticError> {
83        if scale > MAX_SCALE {
84            return Err(ArithmeticError::ScaleExceeded);
85        }
86        Ok(Self { mantissa, scale })
87    }
88
89    /// Construct without validating scale. Caller must ensure `scale <= MAX_SCALE`.
90    ///
91    /// # Safety (logical)
92    /// This is not `unsafe` in the Rust sense, but violating the scale invariant
93    /// will cause incorrect arithmetic results. Prefer `Decimal::new`.
94    #[inline]
95    pub(crate) const fn new_unchecked(mantissa: i128, scale: u8) -> Self {
96        Self { mantissa, scale }
97    }
98
99    /// Returns the raw mantissa.
100    #[inline]
101    pub fn mantissa(self) -> i128 {
102        self.mantissa
103    }
104
105    /// Returns the scale (number of decimal places).
106    #[inline]
107    pub fn scale(self) -> u8 {
108        self.scale
109    }
110
111    /// Returns `true` if the value is exactly zero.
112    #[inline]
113    pub fn is_zero(self) -> bool {
114        self.mantissa == 0
115    }
116
117    /// Returns `true` if the value is strictly positive.
118    #[inline]
119    pub fn is_positive(self) -> bool {
120        self.mantissa > 0
121    }
122
123    /// Returns `true` if the value is strictly negative.
124    #[inline]
125    pub fn is_negative(self) -> bool {
126        self.mantissa < 0
127    }
128}