nautilus_model/types/
fixed.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Functions for handling fixed-point arithmetic.
17//!
18//! This module provides constants and functions that enforce a fixed-point precision strategy,
19//! ensuring consistent precision and scaling across various types and calculations.
20
21use nautilus_core::correctness::FAILED;
22
23/// Indicates if high-precision mode is enabled.
24///
25/// # Safety
26///
27/// This static variable is initialized at compile time and never mutated,
28/// making it safe to read from multiple threads without synchronization.
29/// The value is determined by the "high-precision" feature flag.
30#[unsafe(no_mangle)]
31#[allow(unsafe_code)]
32pub static HIGH_PRECISION_MODE: u8 = cfg!(feature = "high-precision") as u8;
33
34// -----------------------------------------------------------------------------
35// FIXED_PRECISION
36// -----------------------------------------------------------------------------
37
38#[cfg(feature = "high-precision")]
39/// The maximum fixed-point precision.
40pub const FIXED_PRECISION: u8 = 16;
41
42#[cfg(not(feature = "high-precision"))]
43/// The maximum fixed-point precision.
44pub const FIXED_PRECISION: u8 = 9;
45
46// -----------------------------------------------------------------------------
47// PRECISION_BYTES (size of integer backing the fixed-point values)
48// -----------------------------------------------------------------------------
49
50#[cfg(feature = "high-precision")]
51/// The width in bytes for fixed-point value types in high-precision mode (128-bit).
52pub const PRECISION_BYTES: i32 = 16;
53
54#[cfg(not(feature = "high-precision"))]
55/// The width in bytes for fixed-point value types in standard-precision mode (64-bit).
56pub const PRECISION_BYTES: i32 = 8;
57
58// -----------------------------------------------------------------------------
59// FIXED_BINARY_SIZE
60// -----------------------------------------------------------------------------
61
62#[cfg(feature = "high-precision")]
63/// The data type name for the Arrow fixed-size binary representation.
64pub const FIXED_SIZE_BINARY: &str = "FixedSizeBinary(16)";
65
66#[cfg(not(feature = "high-precision"))]
67/// The data type name for the Arrow fixed-size binary representation.
68pub const FIXED_SIZE_BINARY: &str = "FixedSizeBinary(8)";
69
70// -----------------------------------------------------------------------------
71// FIXED_SCALAR
72// -----------------------------------------------------------------------------
73
74#[cfg(feature = "high-precision")]
75/// The scalar value corresponding to the maximum precision (10^16).
76pub const FIXED_SCALAR: f64 = 10_000_000_000_000_000.0;
77
78#[cfg(not(feature = "high-precision"))]
79/// The scalar value corresponding to the maximum precision (10^9).
80pub const FIXED_SCALAR: f64 = 1_000_000_000.0;
81
82// -----------------------------------------------------------------------------
83// PRECISION_DIFF_SCALAR
84// -----------------------------------------------------------------------------
85
86#[cfg(feature = "high-precision")]
87/// The scalar representing the difference between high-precision and standard-precision modes.
88pub const PRECISION_DIFF_SCALAR: f64 = 10_000_000.0; // 10^(16-9)
89
90#[cfg(not(feature = "high-precision"))]
91/// The scalar representing the difference between high-precision and standard-precision modes.
92pub const PRECISION_DIFF_SCALAR: f64 = 1.0;
93
94// -----------------------------------------------------------------------------
95
96/// The maximum precision that can be safely used with f64-based constructors.
97///
98/// This is a hard limit imposed by IEEE 754 double-precision floating-point representation,
99/// which has approximately 15-17 significant decimal digits. Beyond 16 decimal places,
100/// floating-point arithmetic becomes unreliable due to rounding errors.
101///
102/// For higher precision values (such as 18-decimal wei values in DeFi), specialized
103/// constructors that work with integer representations should be used instead.
104pub const MAX_FLOAT_PRECISION: u8 = 16;
105
106/// Checks if a given `precision` value is within the allowed fixed-point precision range.
107///
108/// # Errors
109///
110/// Returns an error if `precision` exceeds [`FIXED_PRECISION`].
111pub fn check_fixed_precision(precision: u8) -> anyhow::Result<()> {
112    #[cfg(feature = "defi")]
113    if precision > crate::defi::WEI_PRECISION {
114        anyhow::bail!("`precision` exceeded maximum `WEI_PRECISION` (18), was {precision}")
115    }
116
117    #[cfg(not(feature = "defi"))]
118    if precision > FIXED_PRECISION {
119        anyhow::bail!(
120            "`precision` exceeded maximum `FIXED_PRECISION` ({FIXED_PRECISION}), was {precision}"
121        )
122    }
123
124    Ok(())
125}
126
127/// Converts an `f64` value to a raw fixed-point `i64` representation with a specified precision.
128///
129/// # Precision and Rounding
130///
131/// This function performs IEEE 754 "round half to even" rounding at the specified precision
132/// before scaling to the fixed-point representation. The rounding is intentionally applied
133/// at the user-specified precision level to ensure values are correctly represented
134/// without accumulating floating-point errors during scaling.
135///
136/// # Panics
137///
138/// Panics if `precision` exceeds [`FIXED_PRECISION`].
139#[must_use]
140pub fn f64_to_fixed_i64(value: f64, precision: u8) -> i64 {
141    check_fixed_precision(precision).expect(FAILED);
142    let pow1 = 10_i64.pow(u32::from(precision));
143    let pow2 = 10_i64.pow(u32::from(FIXED_PRECISION - precision));
144    let rounded = (value * pow1 as f64).round() as i64;
145    rounded * pow2
146}
147
148/// Converts an `f64` value to a raw fixed-point `i128` representation with a specified precision.
149///
150/// # Panics
151///
152/// Panics if `precision` exceeds [`FIXED_PRECISION`].
153pub fn f64_to_fixed_i128(value: f64, precision: u8) -> i128 {
154    check_fixed_precision(precision).expect(FAILED);
155    let pow1 = 10_i128.pow(u32::from(precision));
156    let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
157    let rounded = (value * pow1 as f64).round() as i128;
158    rounded * pow2
159}
160
161/// Converts an `f64` value to a raw fixed-point `u64` representation with a specified precision.
162///
163/// # Panics
164///
165/// Panics if `precision` exceeds [`FIXED_PRECISION`].
166#[must_use]
167pub fn f64_to_fixed_u64(value: f64, precision: u8) -> u64 {
168    check_fixed_precision(precision).expect(FAILED);
169    let pow1 = 10_u64.pow(u32::from(precision));
170    let pow2 = 10_u64.pow(u32::from(FIXED_PRECISION - precision));
171    let rounded = (value * pow1 as f64).round() as u64;
172    rounded * pow2
173}
174
175/// Converts an `f64` value to a raw fixed-point `u128` representation with a specified precision.
176///
177/// # Panics
178///
179/// Panics if `precision` exceeds [`FIXED_PRECISION`].
180#[must_use]
181pub fn f64_to_fixed_u128(value: f64, precision: u8) -> u128 {
182    check_fixed_precision(precision).expect(FAILED);
183    let pow1 = 10_u128.pow(u32::from(precision));
184    let pow2 = 10_u128.pow(u32::from(FIXED_PRECISION - precision));
185    let rounded = (value * pow1 as f64).round() as u128;
186    rounded * pow2
187}
188
189/// Converts a raw fixed-point `i64` value back to an `f64` value.
190#[must_use]
191pub fn fixed_i64_to_f64(value: i64) -> f64 {
192    (value as f64) / FIXED_SCALAR
193}
194
195/// Converts a raw fixed-point `i128` value back to an `f64` value.
196#[must_use]
197pub fn fixed_i128_to_f64(value: i128) -> f64 {
198    (value as f64) / FIXED_SCALAR
199}
200
201/// Converts a raw fixed-point `u64` value back to an `f64` value.
202#[must_use]
203pub fn fixed_u64_to_f64(value: u64) -> f64 {
204    (value as f64) / FIXED_SCALAR
205}
206
207/// Converts a raw fixed-point `u128` value back to an `f64` value.
208#[must_use]
209pub fn fixed_u128_to_f64(value: u128) -> f64 {
210    (value as f64) / FIXED_SCALAR
211}
212
213////////////////////////////////////////////////////////////////////////////////
214// Tests
215////////////////////////////////////////////////////////////////////////////////
216#[cfg(feature = "high-precision")]
217#[cfg(test)]
218mod tests {
219    use nautilus_core::approx_eq;
220    use rstest::rstest;
221
222    use super::*;
223
224    #[cfg(not(feature = "high-precision"))]
225    #[rstest]
226    fn test_precision_boundaries() {
227        assert!(check_fixed_precision(0).is_ok());
228        assert!(check_fixed_precision(FIXED_PRECISION).is_ok());
229        assert!(check_fixed_precision(FIXED_PRECISION + 1).is_err());
230    }
231
232    #[cfg(feature = "defi")]
233    #[rstest]
234    fn test_precision_boundaries() {
235        use crate::defi::WEI_PRECISION;
236
237        assert!(check_fixed_precision(0).is_ok());
238        assert!(check_fixed_precision(WEI_PRECISION).is_ok());
239        assert!(check_fixed_precision(WEI_PRECISION + 1).is_err());
240    }
241
242    #[rstest]
243    #[case(0.0)]
244    #[case(1.0)]
245    #[case(-1.0)]
246    fn test_basic_roundtrip(#[case] value: f64) {
247        for precision in 0..=FIXED_PRECISION {
248            let fixed = f64_to_fixed_i128(value, precision);
249            let result = fixed_i128_to_f64(fixed);
250            assert!(approx_eq!(f64, value, result, epsilon = 0.001, ulps = 16));
251        }
252    }
253
254    #[rstest]
255    #[case(1000000.0)]
256    #[case(-1000000.0)]
257    fn test_large_value_roundtrip(#[case] value: f64) {
258        for precision in 0..=FIXED_PRECISION {
259            let fixed = f64_to_fixed_i128(value, precision);
260            let result = fixed_i128_to_f64(fixed);
261            assert!(approx_eq!(f64, value, result, epsilon = 0.000_1));
262        }
263    }
264
265    #[rstest]
266    #[case(0, 123456.0)]
267    #[case(0, 123456.7)]
268    #[case(1, 123456.7)]
269    #[case(2, 123456.78)]
270    #[case(8, 123456.12345678)]
271    fn test_precision_specific_values_basic(#[case] precision: u8, #[case] value: f64) {
272        let result = f64_to_fixed_i128(value, precision);
273        let back_converted = fixed_i128_to_f64(result);
274        // Round-trip should preserve the value up to the specified precision
275        let scale = 10.0_f64.powi(precision as i32);
276        let expected_rounded = (value * scale).round() / scale;
277        assert!((back_converted - expected_rounded).abs() < 1e-10);
278    }
279
280    #[rstest]
281    fn test_max_precision_values() {
282        // Test with maximum precision that the current feature set supports
283        let test_value = 123456.123456789;
284        let result = f64_to_fixed_i128(test_value, FIXED_PRECISION);
285        let back_converted = fixed_i128_to_f64(result);
286        // For maximum precision, we expect some floating-point limitations
287        assert!((back_converted - test_value).abs() < 1e-6);
288    }
289
290    #[rstest]
291    #[case(0.0)]
292    #[case(1.0)]
293    #[case(1000000.0)]
294    fn test_unsigned_basic_roundtrip(#[case] value: f64) {
295        for precision in 0..=FIXED_PRECISION {
296            let fixed = f64_to_fixed_u128(value, precision);
297            let result = fixed_u128_to_f64(fixed);
298            assert!(approx_eq!(f64, value, result, epsilon = 0.001, ulps = 16));
299        }
300    }
301
302    #[rstest]
303    #[case(0)]
304    #[case(FIXED_PRECISION)]
305    fn test_valid_precision(#[case] precision: u8) {
306        let result = check_fixed_precision(precision);
307        assert!(result.is_ok());
308    }
309
310    #[cfg(not(feature = "defi"))]
311    #[rstest]
312    fn test_invalid_precision() {
313        let precision = FIXED_PRECISION + 1;
314        let result = check_fixed_precision(precision);
315        assert!(result.is_err());
316    }
317
318    #[cfg(feature = "defi")]
319    #[rstest]
320    fn test_invalid_precision() {
321        use crate::defi::WEI_PRECISION;
322        let precision = WEI_PRECISION + 1;
323        let result = check_fixed_precision(precision);
324        assert!(result.is_err());
325    }
326
327    #[rstest]
328    #[case(0, 0.0)]
329    #[case(1, 1.0)]
330    #[case(1, 1.1)]
331    #[case(9, 0.000_000_001)]
332    #[case(16, 0.000_000_000_000_000_1)]
333    #[case(0, -0.0)]
334    #[case(1, -1.0)]
335    #[case(1, -1.1)]
336    #[case(9, -0.000_000_001)]
337    #[case(16, -0.000_000_000_000_000_1)]
338    fn test_f64_to_fixed_i128_to_fixed(#[case] precision: u8, #[case] value: f64) {
339        let fixed = f64_to_fixed_i128(value, precision);
340        let result = fixed_i128_to_f64(fixed);
341        assert_eq!(result, value);
342    }
343
344    #[rstest]
345    #[case(0, 0.0)]
346    #[case(1, 1.0)]
347    #[case(1, 1.1)]
348    #[case(9, 0.000_000_001)]
349    #[case(16, 0.000_000_000_000_000_1)]
350    fn test_f64_to_fixed_u128_to_fixed(#[case] precision: u8, #[case] value: f64) {
351        let fixed = f64_to_fixed_u128(value, precision);
352        let result = fixed_u128_to_f64(fixed);
353        assert_eq!(result, value);
354    }
355
356    #[rstest]
357    #[case(0, 123_456.0)]
358    #[case(0, 123_456.7)]
359    #[case(0, 123_456.4)]
360    #[case(1, 123_456.0)]
361    #[case(1, 123_456.7)]
362    #[case(1, 123_456.4)]
363    #[case(2, 123_456.0)]
364    #[case(2, 123_456.7)]
365    #[case(2, 123_456.4)]
366    fn test_f64_to_fixed_i128_with_precision(#[case] precision: u8, #[case] value: f64) {
367        let result = f64_to_fixed_i128(value, precision);
368
369        // Calculate expected value dynamically based on current FIXED_PRECISION
370        let pow1 = 10_i128.pow(u32::from(precision));
371        let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
372        let rounded = (value * pow1 as f64).round() as i128;
373        let expected = rounded * pow2;
374
375        assert_eq!(
376            result, expected,
377            "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
378        );
379    }
380
381    #[rstest]
382    #[case(0, 5.555555555555555)]
383    #[case(1, 5.555555555555555)]
384    #[case(2, 5.555555555555555)]
385    #[case(3, 5.555555555555555)]
386    #[case(4, 5.555555555555555)]
387    #[case(5, 5.555555555555555)]
388    #[case(6, 5.555555555555555)]
389    #[case(7, 5.555555555555555)]
390    #[case(8, 5.555555555555555)]
391    #[case(9, 5.555555555555555)]
392    #[case(10, 5.555555555555555)]
393    #[case(11, 5.555555555555555)]
394    #[case(12, 5.555555555555555)]
395    #[case(13, 5.555555555555555)]
396    #[case(14, 5.555555555555555)]
397    #[case(15, 5.555555555555555)]
398    #[case(0, -5.555555555555555)]
399    #[case(1, -5.555555555555555)]
400    #[case(2, -5.555555555555555)]
401    #[case(3, -5.555555555555555)]
402    #[case(4, -5.555555555555555)]
403    #[case(5, -5.555555555555555)]
404    #[case(6, -5.555555555555555)]
405    #[case(7, -5.555555555555555)]
406    #[case(8, -5.555555555555555)]
407    #[case(9, -5.555555555555555)]
408    #[case(10, -5.555555555555555)]
409    #[case(11, -5.555555555555555)]
410    #[case(12, -5.555555555555555)]
411    #[case(13, -5.555555555555555)]
412    #[case(14, -5.555555555555555)]
413    #[case(15, -5.555555555555555)]
414    fn test_f64_to_fixed_i128(#[case] precision: u8, #[case] value: f64) {
415        // Only test up to the current FIXED_PRECISION
416        if precision > FIXED_PRECISION {
417            return;
418        }
419
420        let result = f64_to_fixed_i128(value, precision);
421
422        // Calculate expected value dynamically based on current FIXED_PRECISION
423        let pow1 = 10_i128.pow(u32::from(precision));
424        let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
425        let rounded = (value * pow1 as f64).round() as i128;
426        let expected = rounded * pow2;
427
428        assert_eq!(
429            result, expected,
430            "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
431        );
432    }
433
434    #[rstest]
435    #[case(0, 5.555555555555555)]
436    #[case(1, 5.555555555555555)]
437    #[case(2, 5.555555555555555)]
438    #[case(3, 5.555555555555555)]
439    #[case(4, 5.555555555555555)]
440    #[case(5, 5.555555555555555)]
441    #[case(6, 5.555555555555555)]
442    #[case(7, 5.555555555555555)]
443    #[case(8, 5.555555555555555)]
444    #[case(9, 5.555555555555555)]
445    #[case(10, 5.555555555555555)]
446    #[case(11, 5.555555555555555)]
447    #[case(12, 5.555555555555555)]
448    #[case(13, 5.555555555555555)]
449    #[case(14, 5.555555555555555)]
450    #[case(15, 5.555555555555555)]
451    #[case(16, 5.555555555555555)]
452    fn test_f64_to_fixed_u64(#[case] precision: u8, #[case] value: f64) {
453        // Only test up to the current FIXED_PRECISION
454        if precision > FIXED_PRECISION {
455            return;
456        }
457
458        let result = f64_to_fixed_u128(value, precision);
459
460        // Calculate expected value dynamically based on current FIXED_PRECISION
461        let pow1 = 10_u128.pow(u32::from(precision));
462        let pow2 = 10_u128.pow(u32::from(FIXED_PRECISION - precision));
463        let rounded = (value * pow1 as f64).round() as u128;
464        let expected = rounded * pow2;
465
466        assert_eq!(
467            result, expected,
468            "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
469        );
470    }
471
472    #[rstest]
473    fn test_fixed_i128_to_f64(
474        #[values(1, -1, 2, -2, 10, -10, 100, -100, 1_000, -1_000, -10_000, -100_000)] value: i128,
475    ) {
476        assert_eq!(fixed_i128_to_f64(value), value as f64 / FIXED_SCALAR);
477    }
478
479    #[rstest]
480    fn test_fixed_u128_to_f64(
481        #[values(
482            0,
483            1,
484            2,
485            3,
486            10,
487            100,
488            1_000,
489            10_000,
490            100_000,
491            1_000_000,
492            10_000_000,
493            100_000_000,
494            1_000_000_000,
495            10_000_000_000,
496            100_000_000_000,
497            1_000_000_000_000,
498            10_000_000_000_000,
499            100_000_000_000_000,
500            1_000_000_000_000_000,
501            10_000_000_000_000_000,
502            100_000_000_000_000_000,
503            1_000_000_000_000_000_000,
504            10_000_000_000_000_000_000,
505            100_000_000_000_000_000_000
506        )]
507        value: u128,
508    ) {
509        let result = fixed_u128_to_f64(value);
510        assert_eq!(result, (value as f64) / FIXED_SCALAR);
511    }
512}
513
514#[cfg(not(feature = "high-precision"))]
515#[cfg(test)]
516mod tests {
517    use nautilus_core::approx_eq;
518    use rstest::rstest;
519
520    use super::*;
521
522    #[rstest]
523    fn test_precision_boundaries() {
524        assert!(check_fixed_precision(0).is_ok());
525        assert!(check_fixed_precision(FIXED_PRECISION).is_ok());
526        assert!(check_fixed_precision(FIXED_PRECISION + 1).is_err());
527    }
528
529    #[rstest]
530    #[case(0.0)]
531    #[case(1.0)]
532    #[case(-1.0)]
533    fn test_basic_roundtrip(#[case] value: f64) {
534        for precision in 0..=FIXED_PRECISION {
535            let fixed = f64_to_fixed_i64(value, precision);
536            let result = fixed_i64_to_f64(fixed);
537            assert!(approx_eq!(f64, value, result, epsilon = 0.001, ulps = 16));
538        }
539    }
540
541    #[rstest]
542    #[case(1000000.0)]
543    #[case(-1000000.0)]
544    fn test_large_value_roundtrip(#[case] value: f64) {
545        for precision in 0..=FIXED_PRECISION {
546            let fixed = f64_to_fixed_i64(value, precision);
547            let result = fixed_i64_to_f64(fixed);
548            assert!(approx_eq!(f64, value, result, epsilon = 0.000_1));
549        }
550    }
551
552    #[rstest]
553    #[case(0, 123456.0, 123456_000000000)]
554    #[case(0, 123456.7, 123457_000000000)]
555    #[case(1, 123456.7, 123456_700000000)]
556    #[case(2, 123456.78, 123456_780000000)]
557    #[case(8, 123456.12345678, 123456_123456780)]
558    #[case(9, 123456.123456789, 123456_123456789)]
559    fn test_precision_specific_values(
560        #[case] precision: u8,
561        #[case] value: f64,
562        #[case] expected: i64,
563    ) {
564        assert_eq!(f64_to_fixed_i64(value, precision), expected);
565    }
566
567    #[rstest]
568    #[case(0.0)]
569    #[case(1.0)]
570    #[case(1000000.0)]
571    fn test_unsigned_basic_roundtrip(#[case] value: f64) {
572        for precision in 0..=FIXED_PRECISION {
573            let fixed = f64_to_fixed_u64(value, precision);
574            let result = fixed_u64_to_f64(fixed);
575            assert!(approx_eq!(f64, value, result, epsilon = 0.001, ulps = 16));
576        }
577    }
578
579    #[rstest]
580    #[case(0, 1.4, 1.0)]
581    #[case(0, 1.5, 2.0)]
582    #[case(0, 1.6, 2.0)]
583    #[case(1, 1.44, 1.4)]
584    #[case(1, 1.45, 1.5)]
585    #[case(1, 1.46, 1.5)]
586    #[case(2, 1.444, 1.44)]
587    #[case(2, 1.445, 1.45)]
588    #[case(2, 1.446, 1.45)]
589    fn test_rounding(#[case] precision: u8, #[case] input: f64, #[case] expected: f64) {
590        let fixed = f64_to_fixed_i128(input, precision);
591        assert!(approx_eq!(
592            f64,
593            fixed_i128_to_f64(fixed),
594            expected,
595            epsilon = 0.000_000_001
596        ));
597    }
598
599    #[rstest]
600    fn test_special_values() {
601        // Zero handling
602        assert_eq!(f64_to_fixed_i128(0.0, FIXED_PRECISION), 0);
603        assert_eq!(f64_to_fixed_i128(-0.0, FIXED_PRECISION), 0);
604
605        // Small values
606        let smallest_positive = 1.0 / FIXED_SCALAR;
607        let fixed_smallest = f64_to_fixed_i128(smallest_positive, FIXED_PRECISION);
608        assert_eq!(fixed_smallest, 1);
609
610        // Large integers
611        let large_int = 1_000_000_000.0;
612        let fixed_large = f64_to_fixed_i128(large_int, 0);
613        assert_eq!(fixed_i128_to_f64(fixed_large), large_int);
614    }
615
616    #[rstest]
617    #[case(0)]
618    #[case(FIXED_PRECISION)]
619    fn test_valid_precision(#[case] precision: u8) {
620        let result = check_fixed_precision(precision);
621        assert!(result.is_ok());
622    }
623
624    #[rstest]
625    fn test_invalid_precision() {
626        let precision = FIXED_PRECISION + 1;
627        let result = check_fixed_precision(precision);
628        assert!(result.is_err());
629    }
630
631    #[rstest]
632    #[case(0, 0.0)]
633    #[case(1, 1.0)]
634    #[case(1, 1.1)]
635    #[case(9, 0.000_000_001)]
636    #[case(0, -0.0)]
637    #[case(1, -1.0)]
638    #[case(1, -1.1)]
639    #[case(9, -0.000_000_001)]
640    fn test_f64_to_fixed_i64_to_fixed(#[case] precision: u8, #[case] value: f64) {
641        let fixed = f64_to_fixed_i64(value, precision);
642        let result = fixed_i64_to_f64(fixed);
643        assert_eq!(result, value);
644    }
645
646    #[rstest]
647    #[case(0, 0.0)]
648    #[case(1, 1.0)]
649    #[case(1, 1.1)]
650    #[case(9, 0.000_000_001)]
651    fn test_f64_to_fixed_u64_to_fixed(#[case] precision: u8, #[case] value: f64) {
652        let fixed = f64_to_fixed_u64(value, precision);
653        let result = fixed_u64_to_f64(fixed);
654        assert_eq!(result, value);
655    }
656
657    #[rstest]
658    #[case(0, 123_456.0, 123_456_000_000_000)]
659    #[case(0, 123_456.7, 123_457_000_000_000)]
660    #[case(0, 123_456.4, 123_456_000_000_000)]
661    #[case(1, 123_456.0, 123_456_000_000_000)]
662    #[case(1, 123_456.7, 123_456_700_000_000)]
663    #[case(1, 123_456.4, 123_456_400_000_000)]
664    #[case(2, 123_456.0, 123_456_000_000_000)]
665    #[case(2, 123_456.7, 123_456_700_000_000)]
666    #[case(2, 123_456.4, 123_456_400_000_000)]
667    fn test_f64_to_fixed_i64_with_precision(
668        #[case] precision: u8,
669        #[case] value: f64,
670        #[case] expected: i64,
671    ) {
672        assert_eq!(f64_to_fixed_i64(value, precision), expected);
673    }
674
675    #[rstest]
676    #[case(0, 5.5, 6_000_000_000)]
677    #[case(1, 5.55, 5_600_000_000)]
678    #[case(2, 5.555, 5_560_000_000)]
679    #[case(3, 5.5555, 5_556_000_000)]
680    #[case(4, 5.55555, 5_555_600_000)]
681    #[case(5, 5.555_555, 5_555_560_000)]
682    #[case(6, 5.555_555_5, 5_555_556_000)]
683    #[case(7, 5.555_555_55, 5_555_555_600)]
684    #[case(8, 5.555_555_555, 5_555_555_560)]
685    #[case(9, 5.555_555_555_5, 5_555_555_556)]
686    #[case(0, -5.5, -6_000_000_000)]
687    #[case(1, -5.55, -5_600_000_000)]
688    #[case(2, -5.555, -5_560_000_000)]
689    #[case(3, -5.5555, -5_556_000_000)]
690    #[case(4, -5.55555, -5_555_600_000)]
691    #[case(5, -5.555_555, -5_555_560_000)]
692    #[case(6, -5.555_555_5, -5_555_556_000)]
693    #[case(7, -5.555_555_55, -5_555_555_600)]
694    #[case(8, -5.555_555_555, -5_555_555_560)]
695    #[case(9, -5.555_555_555_5, -5_555_555_556)]
696    fn test_f64_to_fixed_i64(#[case] precision: u8, #[case] value: f64, #[case] expected: i64) {
697        assert_eq!(f64_to_fixed_i64(value, precision), expected);
698    }
699
700    #[rstest]
701    #[case(0, 5.5, 6_000_000_000)]
702    #[case(1, 5.55, 5_600_000_000)]
703    #[case(2, 5.555, 5_560_000_000)]
704    #[case(3, 5.5555, 5_556_000_000)]
705    #[case(4, 5.55555, 5_555_600_000)]
706    #[case(5, 5.555_555, 5_555_560_000)]
707    #[case(6, 5.555_555_5, 5_555_556_000)]
708    #[case(7, 5.555_555_55, 5_555_555_600)]
709    #[case(8, 5.555_555_555, 5_555_555_560)]
710    #[case(9, 5.555_555_555_5, 5_555_555_556)]
711    fn test_f64_to_fixed_u64(#[case] precision: u8, #[case] value: f64, #[case] expected: u64) {
712        assert_eq!(f64_to_fixed_u64(value, precision), expected);
713    }
714
715    #[rstest]
716    fn test_fixed_i64_to_f64(
717        #[values(1, -1, 2, -2, 10, -10, 100, -100, 1_000, -1_000)] value: i64,
718    ) {
719        assert_eq!(fixed_i64_to_f64(value), value as f64 / FIXED_SCALAR);
720    }
721
722    #[rstest]
723    fn test_fixed_u64_to_f64(
724        #[values(
725            0,
726            1,
727            2,
728            3,
729            10,
730            100,
731            1_000,
732            10_000,
733            100_000,
734            1_000_000,
735            10_000_000,
736            100_000_000,
737            1_000_000_000,
738            10_000_000_000,
739            100_000_000_000,
740            1_000_000_000_000,
741            10_000_000_000_000,
742            100_000_000_000_000,
743            1_000_000_000_000_000
744        )]
745        value: u64,
746    ) {
747        let result = fixed_u64_to_f64(value);
748        assert_eq!(result, (value as f64) / FIXED_SCALAR);
749    }
750}