1use nautilus_model::types::{
23 Price, Quantity, fixed::FIXED_PRECISION, price::PriceRaw, quantity::QuantityRaw,
24};
25
26const POWERS_OF_10: [i64; 19] = [
28 1,
29 10,
30 100,
31 1_000,
32 10_000,
33 100_000,
34 1_000_000,
35 10_000_000,
36 100_000_000,
37 1_000_000_000,
38 10_000_000_000,
39 100_000_000_000,
40 1_000_000_000_000,
41 10_000_000_000_000,
42 100_000_000_000_000,
43 1_000_000_000_000_000,
44 10_000_000_000_000_000,
45 100_000_000_000_000_000,
46 1_000_000_000_000_000_000,
47];
48
49#[inline]
51fn pow10(exp: u8) -> i64 {
52 POWERS_OF_10[exp as usize]
53}
54
55#[inline]
57fn scale_mantissa(mantissa: i64, exponent: i8) -> i64 {
58 let scale_exp = FIXED_PRECISION as i8 + exponent;
59
60 if scale_exp >= 0 {
61 mantissa
62 .checked_mul(pow10(scale_exp as u8))
63 .expect("Overflow scaling mantissa")
64 } else {
65 mantissa / pow10((-scale_exp) as u8)
67 }
68}
69
70#[must_use]
78pub fn mantissa_to_price(mantissa: i64, exponent: i8, precision: u8) -> Price {
79 let raw = scale_mantissa(mantissa, exponent);
80 Price::from_raw(raw as PriceRaw, precision)
81}
82
83#[must_use]
92pub fn mantissa_to_quantity(mantissa: i64, exponent: i8, precision: u8) -> Quantity {
93 let raw = scale_mantissa(mantissa, exponent);
94 assert!(raw >= 0, "Quantity cannot be negative: {raw}");
95 Quantity::from_raw(raw as QuantityRaw, precision)
96}
97
98#[must_use]
103#[inline]
104pub fn mantissa_to_f64(mantissa: i64, exponent: i8) -> f64 {
105 mantissa as f64 * 10_f64.powi(exponent as i32)
106}
107
108#[cfg(test)]
109mod tests {
110 use nautilus_core::approx_eq;
111 use rstest::rstest;
112
113 use super::*;
114
115 #[rstest]
116 #[case(12345678, -8, 8, 0.12345678)]
117 #[case(9876543210, -8, 8, 98.7654321)]
118 #[case(100000000, -8, 8, 1.0)]
119 #[case(50000, -2, 2, 500.0)]
120 #[case(123, 0, 0, 123.0)]
121 fn test_mantissa_to_price(
122 #[case] mantissa: i64,
123 #[case] exponent: i8,
124 #[case] precision: u8,
125 #[case] expected: f64,
126 ) {
127 let price = mantissa_to_price(mantissa, exponent, precision);
128 assert!(
129 approx_eq!(f64, price.as_f64(), expected, epsilon = 1e-10),
130 "Expected {expected}, got {}",
131 price.as_f64()
132 );
133 assert_eq!(price.precision, precision);
134 }
135
136 #[rstest]
137 #[case(12345678, -8, 8, 0.12345678)]
138 #[case(100000000, -8, 8, 1.0)]
139 #[case(50000, -2, 2, 500.0)]
140 fn test_mantissa_to_quantity(
141 #[case] mantissa: i64,
142 #[case] exponent: i8,
143 #[case] precision: u8,
144 #[case] expected: f64,
145 ) {
146 let qty = mantissa_to_quantity(mantissa, exponent, precision);
147 assert!(
148 approx_eq!(f64, qty.as_f64(), expected, epsilon = 1e-10),
149 "Expected {expected}, got {}",
150 qty.as_f64()
151 );
152 assert_eq!(qty.precision, precision);
153 }
154
155 #[rstest]
156 fn test_mantissa_to_f64() {
157 assert!(approx_eq!(
158 f64,
159 mantissa_to_f64(12345678, -8),
160 0.12345678,
161 epsilon = 1e-15
162 ));
163 assert!(approx_eq!(
164 f64,
165 mantissa_to_f64(100, 2),
166 10000.0,
167 epsilon = 1e-10
168 ));
169 }
170
171 #[rstest]
172 #[should_panic(expected = "Quantity cannot be negative")]
173 fn test_negative_quantity_panics() {
174 let _ = mantissa_to_quantity(-100, 0, 0);
175 }
176}