gmsol/utils/
fixed.rs

1use gmsol_store::constants::MARKET_DECIMALS;
2use rust_decimal::Decimal;
3
4const MAX_REPR: u128 = 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF;
5const TARGET_SCALE: u32 = MAX_REPR.ilog10() - 1;
6
7/// Convert signed USD value to [`Decimal`].
8///
9/// # Examples
10///
11/// ```
12/// use gmsol::utils::signed_value_to_decimal;
13/// use rust_decimal_macros::dec;
14///
15/// assert_eq!(
16///     signed_value_to_decimal(-429_663_361_044_608_151),
17///     dec!(-0.00429663361044608151),
18/// );
19/// ```
20pub fn signed_value_to_decimal(num: i128) -> Decimal {
21    signed_fixed_to_decimal(num, MARKET_DECIMALS).expect("must be `Some`")
22}
23
24/// Convert unsigned USD value to [`Decimal`].
25///
26/// # Examples
27///
28/// ```
29/// use gmsol::utils::unsigned_value_to_decimal;
30/// use rust_decimal_macros::dec;
31///
32/// assert_eq!(
33///     unsigned_value_to_decimal(429_663_361_044_608_151),
34///     dec!(0.00429663361044608151),
35/// );
36/// ```
37pub fn unsigned_value_to_decimal(num: u128) -> Decimal {
38    unsigned_fixed_to_decimal(num, MARKET_DECIMALS).expect("must be `Some`")
39}
40
41/// Convert unsigned fixed-point amount to [`Decimal`].
42///
43/// # Examples
44///
45/// ```
46/// use gmsol::utils::unsigned_amount_to_decimal;
47/// use rust_decimal_macros::dec;
48///
49/// assert_eq!(
50///     unsigned_amount_to_decimal(100_451_723_195, 6),
51///     dec!(100_451.723195),
52/// );
53/// ```
54pub fn unsigned_amount_to_decimal(mut num: u64, mut decimals: u8) -> Decimal {
55    const MAX_DECIMALS: u8 = 28;
56    const MAX_SCALE_FOR_U64: u8 = 19;
57
58    if decimals > MAX_DECIMALS {
59        let scale_diff = decimals - MAX_DECIMALS;
60        if scale_diff > MAX_SCALE_FOR_U64 {
61            return Decimal::ZERO;
62        }
63        num /= 10u64.pow(scale_diff as u32);
64        decimals = MAX_DECIMALS;
65    }
66
67    unsigned_fixed_to_decimal(num as u128, decimals).expect("must be `Some`")
68}
69
70/// Convert signed fixed-point amount to [`Decimal`].
71///
72/// # Examples
73///
74/// ```
75/// use gmsol::utils::signed_amount_to_decimal;
76/// use rust_decimal_macros::dec;
77///
78/// assert_eq!(
79///     signed_amount_to_decimal(-100_451_723_195, 6),
80///     dec!(-100_451.723195),
81/// );
82/// ```
83pub fn signed_amount_to_decimal(num: i64, decimals: u8) -> Decimal {
84    let is_negative = num.is_negative();
85    let d = unsigned_amount_to_decimal(num.unsigned_abs(), decimals);
86    if is_negative {
87        -d
88    } else {
89        d
90    }
91}
92
93/// Convert unsigned fixed-point number to [`Decimal`].
94///
95/// Returns `None` if it cannot be represented as a [`Decimal`].
96///
97/// # Examples
98///
99/// ```
100/// use gmsol::utils::unsigned_fixed_to_decimal;
101/// use rust_decimal_macros::dec;
102///
103/// assert_eq!(
104///     unsigned_fixed_to_decimal(100_451_723_195, 6),
105///     Some(dec!(100_451.723195)),
106/// );
107///
108/// assert_eq!(
109///     unsigned_fixed_to_decimal(u128::MAX, 10),
110///     None,
111/// );
112/// ```
113pub fn unsigned_fixed_to_decimal(num: u128, decimals: u8) -> Option<Decimal> {
114    fn convert_by_change_the_scale(mut num: u128, scale: u32) -> Option<Decimal> {
115        let digits = num.ilog10();
116        debug_assert!(digits >= TARGET_SCALE);
117        let scale_diff = digits - TARGET_SCALE;
118        if scale < scale_diff {
119            return None;
120        }
121        num /= 10u128.pow(scale_diff);
122        Some(Decimal::from_i128_with_scale(
123            num as i128,
124            scale - scale_diff,
125        ))
126    }
127
128    let scale = decimals as u32;
129    if num > MAX_REPR {
130        convert_by_change_the_scale(num, scale)
131    } else {
132        Decimal::try_from_i128_with_scale(num as i128, scale).ok()
133    }
134}
135
136/// Convert signed fixed-point value to [`Decimal`].
137///
138/// Returns `None` if it cannot be represented as a [`Decimal`].
139///
140/// # Examples
141///
142/// ```
143/// use gmsol::utils::signed_fixed_to_decimal;
144/// use rust_decimal_macros::dec;
145///
146/// assert_eq!(
147///     signed_fixed_to_decimal(-100_451_723_195, 6),
148///     Some(dec!(-100_451.723195)),
149/// );
150///
151/// assert_eq!(
152///     signed_fixed_to_decimal(i128::MIN, 10),
153///     None,
154/// );
155/// ```
156pub fn signed_fixed_to_decimal(num: i128, decimals: u8) -> Option<Decimal> {
157    let is_negative = num.is_negative();
158    let d = unsigned_fixed_to_decimal(num.unsigned_abs(), decimals)?;
159    if is_negative {
160        Some(-d)
161    } else {
162        Some(d)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use rust_decimal_macros::dec;
170
171    #[test]
172    fn test_convert_value_to_decimal() {
173        assert_eq!(
174            signed_value_to_decimal(2_268_944_690_310_400_000_000_000),
175            dec!(22689.446903104)
176        );
177        assert_eq!(
178            signed_value_to_decimal(i128::MAX),
179            dec!(1701411834604692317.316873037),
180        );
181        assert_eq!(
182            signed_value_to_decimal(i128::MIN),
183            dec!(-1701411834604692317.316873037)
184        );
185        assert_eq!(
186            signed_value_to_decimal(0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF),
187            dec!(792281625.14264337593543950335),
188        );
189
190        assert_eq!(
191            unsigned_value_to_decimal(u128::MAX),
192            dec!(3402823669209384634.633746074),
193        );
194        assert_eq!(
195            unsigned_value_to_decimal(0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF),
196            dec!(792281625.14264337593543950335),
197        );
198    }
199
200    #[test]
201    fn test_convert_signed_amount_to_decimal() {
202        assert_eq!(
203            signed_amount_to_decimal(-2_649_941_038_310_943, 6),
204            dec!(-2649941038.310943),
205        );
206
207        assert_eq!(
208            signed_amount_to_decimal(i64::MAX, 0),
209            dec!(9223372036854775807),
210        );
211
212        assert_eq!(
213            signed_amount_to_decimal(i64::MIN, 0),
214            dec!(-9223372036854775808),
215        );
216
217        assert_eq!(
218            signed_amount_to_decimal(1, 28),
219            dec!(0.0000000000000000000000000001),
220        );
221
222        assert_eq!(
223            signed_amount_to_decimal(-1, 28),
224            dec!(-0.0000000000000000000000000001),
225        );
226
227        assert_eq!(
228            signed_amount_to_decimal(i64::MAX, 29),
229            dec!(0.000000000092233720368547758)
230        );
231
232        assert_eq!(
233            signed_amount_to_decimal(i64::MIN, 29),
234            dec!(-0.000000000092233720368547758),
235        );
236
237        assert_eq!(
238            signed_amount_to_decimal(i64::MAX, 46),
239            dec!(0.0000000000000000000000000009),
240        );
241
242        assert_eq!(
243            signed_amount_to_decimal(i64::MIN, 46),
244            dec!(-0.0000000000000000000000000009),
245        );
246
247        assert_eq!(signed_amount_to_decimal(i64::MAX, 47), Decimal::ZERO);
248
249        assert_eq!(signed_amount_to_decimal(i64::MIN, 47), Decimal::ZERO);
250    }
251
252    #[test]
253    fn test_convert_unsigned_amount_to_decimal() {
254        assert_eq!(
255            unsigned_amount_to_decimal(2_649_941_038_310_943, 6),
256            dec!(2649941038.310943),
257        );
258
259        assert_eq!(
260            unsigned_amount_to_decimal(u64::MAX, 0),
261            dec!(18446744073709551615),
262        );
263
264        assert_eq!(
265            unsigned_amount_to_decimal(u64::MAX, 29),
266            dec!(0.0000000001844674407370955161),
267        );
268
269        assert_eq!(
270            unsigned_amount_to_decimal(u64::MAX, 47),
271            dec!(0.0000000000000000000000000001)
272        );
273
274        assert_eq!(unsigned_amount_to_decimal(u64::MAX, 48), Decimal::ZERO);
275    }
276}