1use std::fmt::Display;
62
63use nautilus_core::correctness::{
64 CorrectnessError, CorrectnessResult, CorrectnessResultExt, FAILED,
65};
66
67use crate::types::{price::PriceRaw, quantity::QuantityRaw};
68
69#[unsafe(no_mangle)]
77#[allow(unsafe_code)]
78pub static HIGH_PRECISION_MODE: u8 = cfg!(feature = "high-precision") as u8;
79
80#[cfg(feature = "high-precision")]
85pub const FIXED_PRECISION: u8 = 16;
87
88#[cfg(not(feature = "high-precision"))]
89pub const FIXED_PRECISION: u8 = 9;
91
92#[cfg(feature = "high-precision")]
97pub const PRECISION_BYTES: i32 = 16;
99
100#[cfg(not(feature = "high-precision"))]
101pub const PRECISION_BYTES: i32 = 8;
103
104#[cfg(feature = "high-precision")]
109pub const FIXED_SIZE_BINARY: &str = "FixedSizeBinary(16)";
111
112#[cfg(not(feature = "high-precision"))]
113pub const FIXED_SIZE_BINARY: &str = "FixedSizeBinary(8)";
115
116#[cfg(feature = "high-precision")]
121pub const FIXED_SCALAR: f64 = 10_000_000_000_000_000.0;
123
124#[cfg(not(feature = "high-precision"))]
125pub const FIXED_SCALAR: f64 = 1_000_000_000.0;
127
128#[cfg(feature = "high-precision")]
133pub const PRECISION_DIFF_SCALAR: f64 = 10_000_000.0; #[cfg(not(feature = "high-precision"))]
137pub const PRECISION_DIFF_SCALAR: f64 = 1.0;
139
140const POWERS_OF_10: [u64; 17] = [
149 1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000, 10_000_000_000, 100_000_000_000, 1_000_000_000_000, 10_000_000_000_000, 100_000_000_000_000, 1_000_000_000_000_000, 10_000_000_000_000_000, ];
167
168const _: () = assert!(
171 (FIXED_PRECISION as usize) < POWERS_OF_10.len(),
172 "FIXED_PRECISION exceeds POWERS_OF_10 table size"
173);
174
175pub const MAX_FLOAT_PRECISION: u8 = 16;
186
187pub fn check_fixed_precision(precision: u8) -> CorrectnessResult<()> {
195 #[cfg(feature = "defi")]
196 if precision > crate::defi::WEI_PRECISION {
197 return Err(CorrectnessError::PredicateViolation {
198 message: format!("`precision` exceeded maximum `WEI_PRECISION` (18), was {precision}"),
199 });
200 }
201
202 #[cfg(not(feature = "defi"))]
203 if precision > FIXED_PRECISION {
204 return Err(CorrectnessError::PredicateViolation {
205 message: format!(
206 "`precision` exceeded maximum `FIXED_PRECISION` ({FIXED_PRECISION}), was {precision}"
207 ),
208 });
209 }
210
211 Ok(())
212}
213
214#[inline]
225#[must_use]
226pub fn raw_scales_match(a: u8, b: u8) -> bool {
227 a.max(FIXED_PRECISION) == b.max(FIXED_PRECISION)
228}
229
230#[inline(always)]
240fn should_skip_validation(precision: u8) -> bool {
241 if precision == FIXED_PRECISION {
242 return true;
243 }
244
245 if precision > FIXED_PRECISION {
246 #[cfg(not(feature = "defi"))]
248 debug_assert!(
249 false,
250 "precision {precision} exceeds FIXED_PRECISION {FIXED_PRECISION}: \
251 raw value validation is not possible at this precision"
252 );
253 return true;
254 }
255
256 false
257}
258
259#[cold]
261fn invalid_raw_error(
262 raw: impl Display,
263 precision: u8,
264 remainder: impl Display,
265 scale: impl Display,
266) -> anyhow::Error {
267 anyhow::anyhow!(
268 "Invalid fixed-point raw value {raw} for precision {precision}: \
269 remainder {remainder} when divided by scale {scale}. \
270 Raw value should be a multiple of {scale}. \
271 This indicates data corruption or incorrect precision/scaling upstream"
272 )
273}
274
275#[inline(always)]
305pub fn check_fixed_raw_u128(raw: u128, precision: u8) -> anyhow::Result<()> {
306 if should_skip_validation(precision) {
307 return Ok(());
308 }
309
310 let exp = usize::from(FIXED_PRECISION - precision);
311 let scale = u128::from(POWERS_OF_10[exp]);
312 let remainder = raw % scale;
313
314 if remainder != 0 {
315 return Err(invalid_raw_error(raw, precision, remainder, scale));
316 }
317
318 Ok(())
319}
320
321#[inline(always)]
330pub fn check_fixed_raw_u64(raw: u64, precision: u8) -> anyhow::Result<()> {
331 if should_skip_validation(precision) {
332 return Ok(());
333 }
334
335 let exp = usize::from(FIXED_PRECISION - precision);
336 let scale = POWERS_OF_10[exp];
337 let remainder = raw % scale;
338
339 if remainder != 0 {
340 return Err(invalid_raw_error(raw, precision, remainder, scale));
341 }
342
343 Ok(())
344}
345
346#[inline(always)]
376pub fn check_fixed_raw_i128(raw: i128, precision: u8) -> anyhow::Result<()> {
377 if should_skip_validation(precision) {
378 return Ok(());
379 }
380
381 let exp = usize::from(FIXED_PRECISION - precision);
382 let scale = i128::from(POWERS_OF_10[exp]);
383 let remainder = raw % scale;
384
385 if remainder != 0 {
386 return Err(invalid_raw_error(raw, precision, remainder, scale));
387 }
388
389 Ok(())
390}
391
392#[inline(always)]
401pub fn check_fixed_raw_i64(raw: i64, precision: u8) -> anyhow::Result<()> {
402 if should_skip_validation(precision) {
403 return Ok(());
404 }
405
406 let exp = usize::from(FIXED_PRECISION - precision);
407 let scale = POWERS_OF_10[exp] as i64;
408 let remainder = raw % scale;
409
410 if remainder != 0 {
411 return Err(invalid_raw_error(raw, precision, remainder, scale));
412 }
413
414 Ok(())
415}
416
417#[must_use]
431pub fn correct_raw_u128(raw: u128, precision: u8) -> u128 {
432 if precision >= FIXED_PRECISION {
433 return raw;
434 }
435 let exp = usize::from(FIXED_PRECISION - precision);
436 let scale = u128::from(POWERS_OF_10[exp]);
437 let half_scale = scale / 2;
438 let remainder = raw % scale;
439 if remainder == 0 {
440 raw
441 } else if remainder >= half_scale {
442 raw + (scale - remainder)
443 } else {
444 raw - remainder
445 }
446}
447
448#[must_use]
453pub fn correct_raw_u64(raw: u64, precision: u8) -> u64 {
454 if precision >= FIXED_PRECISION {
455 return raw;
456 }
457 let exp = usize::from(FIXED_PRECISION - precision);
458 let scale = POWERS_OF_10[exp];
459 let half_scale = scale / 2;
460 let remainder = raw % scale;
461 if remainder == 0 {
462 raw
463 } else if remainder >= half_scale {
464 raw + (scale - remainder)
465 } else {
466 raw - remainder
467 }
468}
469
470#[must_use]
475pub fn correct_raw_i128(raw: i128, precision: u8) -> i128 {
476 if precision >= FIXED_PRECISION {
477 return raw;
478 }
479 let exp = usize::from(FIXED_PRECISION - precision);
480 let scale = i128::from(POWERS_OF_10[exp]);
481 let half_scale = scale / 2;
482 let remainder = raw % scale;
483 if remainder == 0 {
484 raw
485 } else if raw >= 0 {
486 if remainder >= half_scale {
487 raw + (scale - remainder)
488 } else {
489 raw - remainder
490 }
491 } else {
492 if remainder.abs() >= half_scale {
494 raw - (scale + remainder)
495 } else {
496 raw - remainder
497 }
498 }
499}
500
501#[must_use]
506pub fn correct_raw_i64(raw: i64, precision: u8) -> i64 {
507 if precision >= FIXED_PRECISION {
508 return raw;
509 }
510 let exp = usize::from(FIXED_PRECISION - precision);
511 let scale = POWERS_OF_10[exp] as i64;
512 let half_scale = scale / 2;
513 let remainder = raw % scale;
514 if remainder == 0 {
515 raw
516 } else if raw >= 0 {
517 if remainder >= half_scale {
518 raw + (scale - remainder)
519 } else {
520 raw - remainder
521 }
522 } else {
523 if remainder.abs() >= half_scale {
525 raw - (scale + remainder)
526 } else {
527 raw - remainder
528 }
529 }
530}
531
532#[must_use]
538#[inline]
539pub fn correct_price_raw(raw: PriceRaw, precision: u8) -> PriceRaw {
540 #[cfg(feature = "high-precision")]
541 {
542 correct_raw_i128(raw, precision)
543 }
544 #[cfg(not(feature = "high-precision"))]
545 {
546 correct_raw_i64(raw, precision)
547 }
548}
549
550#[must_use]
556#[inline]
557pub fn correct_quantity_raw(raw: QuantityRaw, precision: u8) -> QuantityRaw {
558 #[cfg(feature = "high-precision")]
559 {
560 correct_raw_u128(raw, precision)
561 }
562 #[cfg(not(feature = "high-precision"))]
563 {
564 correct_raw_u64(raw, precision)
565 }
566}
567
568#[must_use]
573#[inline]
574pub fn bankers_round(mantissa: i128, excess: u32) -> i128 {
575 if excess == 0 {
576 return mantissa;
577 }
578
579 if excess >= 39 {
581 return 0;
582 }
583
584 let divisor = 10i128.pow(excess);
585 let quotient = mantissa / divisor;
586 let remainder = mantissa % divisor;
587 let half = divisor / 2;
588
589 if remainder.abs() > half || (remainder.abs() == half && quotient % 2 != 0) {
590 quotient + mantissa.signum()
591 } else {
592 quotient
593 }
594}
595
596pub fn mantissa_exponent_to_fixed_i128(
611 mantissa: i128,
612 exponent: i8,
613 precision: u8,
614) -> CorrectnessResult<i128> {
615 check_fixed_precision(precision)?;
616
617 let precision_i16 = i16::from(precision);
618 let target_scale = i16::from(FIXED_PRECISION).max(precision_i16);
619 let frac_digits = -i16::from(exponent);
620
621 let mantissa = if frac_digits > precision_i16 {
622 let excess = (frac_digits - precision_i16) as u32;
623 bankers_round(mantissa, excess)
624 } else {
625 mantissa
626 };
627
628 let scale_after_rounding = frac_digits.min(precision_i16);
629 let scale_exp = target_scale - scale_after_rounding;
630 if scale_exp > 38 {
631 return Err(CorrectnessError::PredicateViolation {
632 message: format!(
633 "Exponent {exponent} produces scale factor 10^{scale_exp} which exceeds i128 range"
634 ),
635 });
636 }
637
638 if scale_exp >= 0 {
639 mantissa.checked_mul(10i128.pow(scale_exp as u32))
640 } else {
641 Some(mantissa / 10i128.pow((-scale_exp) as u32))
642 }
643 .ok_or_else(|| CorrectnessError::PredicateViolation {
644 message: "Overflow when scaling mantissa to fixed precision".to_string(),
645 })
646}
647
648#[must_use]
661pub fn f64_to_fixed_i64(value: f64, precision: u8) -> i64 {
662 check_fixed_precision(precision).expect_display(FAILED);
663 let pow1 = 10_i64.pow(u32::from(precision));
664 let pow2 = 10_i64.pow(u32::from(FIXED_PRECISION - precision));
665 let rounded = (value * pow1 as f64).round() as i64;
666 rounded * pow2
667}
668
669#[must_use]
675pub fn f64_to_fixed_i128(value: f64, precision: u8) -> i128 {
676 check_fixed_precision(precision).expect_display(FAILED);
677 let pow1 = 10_i128.pow(u32::from(precision));
678 let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
679 let rounded = (value * pow1 as f64).round() as i128;
680 rounded * pow2
681}
682
683#[must_use]
689pub fn f64_to_fixed_u64(value: f64, precision: u8) -> u64 {
690 check_fixed_precision(precision).expect_display(FAILED);
691 let pow1 = 10_u64.pow(u32::from(precision));
692 let pow2 = 10_u64.pow(u32::from(FIXED_PRECISION - precision));
693 let rounded = (value * pow1 as f64).round() as u64;
694 rounded * pow2
695}
696
697#[must_use]
703pub fn f64_to_fixed_u128(value: f64, precision: u8) -> u128 {
704 check_fixed_precision(precision).expect_display(FAILED);
705 let pow1 = 10_u128.pow(u32::from(precision));
706 let pow2 = 10_u128.pow(u32::from(FIXED_PRECISION - precision));
707 let rounded = (value * pow1 as f64).round() as u128;
708 rounded * pow2
709}
710
711#[must_use]
713pub fn fixed_i64_to_f64(value: i64) -> f64 {
714 (value as f64) / FIXED_SCALAR
715}
716
717#[must_use]
719pub fn fixed_i128_to_f64(value: i128) -> f64 {
720 (value as f64) / FIXED_SCALAR
721}
722
723#[must_use]
725pub fn fixed_u64_to_f64(value: u64) -> f64 {
726 (value as f64) / FIXED_SCALAR
727}
728
729#[must_use]
731pub fn fixed_u128_to_f64(value: u128) -> f64 {
732 (value as f64) / FIXED_SCALAR
733}
734
735#[cfg(feature = "high-precision")]
736#[cfg(test)]
737mod tests {
738 use nautilus_core::approx_eq;
739 use rstest::rstest;
740
741 use super::*;
742
743 #[cfg(not(feature = "high-precision"))]
744 #[rstest]
745 fn test_precision_boundaries() {
746 assert!(check_fixed_precision(0).is_ok());
747 assert!(check_fixed_precision(FIXED_PRECISION).is_ok());
748 assert!(check_fixed_precision(FIXED_PRECISION + 1).is_err());
749 }
750
751 #[cfg(feature = "defi")]
752 #[rstest]
753 fn test_precision_boundaries() {
754 use crate::defi::WEI_PRECISION;
755
756 assert!(check_fixed_precision(0).is_ok());
757 assert!(check_fixed_precision(WEI_PRECISION).is_ok());
758 assert!(check_fixed_precision(WEI_PRECISION + 1).is_err());
759 }
760
761 #[rstest]
762 #[case(0.0)]
763 #[case(1.0)]
764 #[case(-1.0)]
765 fn test_basic_roundtrip(#[case] value: f64) {
766 for precision in 0..=FIXED_PRECISION {
767 let fixed = f64_to_fixed_i128(value, precision);
768 let result = fixed_i128_to_f64(fixed);
769 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
770 }
771 }
772
773 #[rstest]
774 #[case(1_000_000.0)]
775 #[case(-1_000_000.0)]
776 fn test_large_value_roundtrip(#[case] value: f64) {
777 for precision in 0..=FIXED_PRECISION {
778 let fixed = f64_to_fixed_i128(value, precision);
779 let result = fixed_i128_to_f64(fixed);
780 assert!(approx_eq!(f64, value, result, epsilon = 0.000_1));
781 }
782 }
783
784 #[rstest]
785 #[case(0, 123_456.0)]
786 #[case(0, 123_456.7)]
787 #[case(1, 123_456.7)]
788 #[case(2, 123_456.78)]
789 #[case(8, 123_456.123_456_78)]
790 fn test_precision_specific_values_basic(#[case] precision: u8, #[case] value: f64) {
791 let result = f64_to_fixed_i128(value, precision);
792 let back_converted = fixed_i128_to_f64(result);
793 let scale = 10.0_f64.powi(i32::from(precision));
795 let expected_rounded = (value * scale).round() / scale;
796 assert!((back_converted - expected_rounded).abs() < 1e-10);
797 }
798
799 #[rstest]
800 fn test_max_precision_values() {
801 let test_value = 123_456.123_456_789;
803 let result = f64_to_fixed_i128(test_value, FIXED_PRECISION);
804 let back_converted = fixed_i128_to_f64(result);
805 assert!((back_converted - test_value).abs() < 1e-6);
807 }
808
809 #[rstest]
810 #[case(0.0)]
811 #[case(1.0)]
812 #[case(1_000_000.0)]
813 fn test_unsigned_basic_roundtrip(#[case] value: f64) {
814 for precision in 0..=FIXED_PRECISION {
815 let fixed = f64_to_fixed_u128(value, precision);
816 let result = fixed_u128_to_f64(fixed);
817 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
818 }
819 }
820
821 #[rstest]
822 #[case(0)]
823 #[case(FIXED_PRECISION)]
824 fn test_valid_precision(#[case] precision: u8) {
825 let result = check_fixed_precision(precision);
826 assert!(result.is_ok());
827 }
828
829 #[cfg(not(feature = "defi"))]
830 #[rstest]
831 fn test_invalid_precision() {
832 let precision = FIXED_PRECISION + 1;
833 let result = check_fixed_precision(precision);
834 assert!(result.is_err());
835 }
836
837 #[cfg(feature = "defi")]
838 #[rstest]
839 fn test_invalid_precision() {
840 use crate::defi::WEI_PRECISION;
841 let precision = WEI_PRECISION + 1;
842 let result = check_fixed_precision(precision);
843 assert!(result.is_err());
844 }
845
846 #[cfg(not(feature = "defi"))]
847 #[rstest]
848 fn test_check_fixed_precision_returns_typed_error_with_stable_display() {
849 let error = check_fixed_precision(FIXED_PRECISION + 1).unwrap_err();
850
851 assert_eq!(
852 error,
853 CorrectnessError::PredicateViolation {
854 message: format!(
855 "`precision` exceeded maximum `FIXED_PRECISION` ({FIXED_PRECISION}), was {}",
856 FIXED_PRECISION + 1
857 ),
858 }
859 );
860 assert_eq!(
861 error.to_string(),
862 format!(
863 "`precision` exceeded maximum `FIXED_PRECISION` ({FIXED_PRECISION}), was {}",
864 FIXED_PRECISION + 1
865 )
866 );
867 }
868
869 #[cfg(feature = "defi")]
870 #[rstest]
871 fn test_check_fixed_precision_returns_typed_error_with_stable_display() {
872 use crate::defi::WEI_PRECISION;
873
874 let error = check_fixed_precision(WEI_PRECISION + 1).unwrap_err();
875
876 assert_eq!(
877 error,
878 CorrectnessError::PredicateViolation {
879 message: format!(
880 "`precision` exceeded maximum `WEI_PRECISION` (18), was {}",
881 WEI_PRECISION + 1
882 ),
883 }
884 );
885 assert_eq!(
886 error.to_string(),
887 format!(
888 "`precision` exceeded maximum `WEI_PRECISION` (18), was {}",
889 WEI_PRECISION + 1
890 )
891 );
892 }
893
894 #[rstest]
895 #[case(0, 0.0)]
896 #[case(1, 1.0)]
897 #[case(1, 1.1)]
898 #[case(9, 0.000_000_001)]
899 #[case(16, 0.000_000_000_000_000_1)]
900 #[case(0, -0.0)]
901 #[case(1, -1.0)]
902 #[case(1, -1.1)]
903 #[case(9, -0.000_000_001)]
904 #[case(16, -0.000_000_000_000_000_1)]
905 fn test_f64_to_fixed_i128_to_fixed(#[case] precision: u8, #[case] value: f64) {
906 let fixed = f64_to_fixed_i128(value, precision);
907 let result = fixed_i128_to_f64(fixed);
908 assert_eq!(result, value);
909 }
910
911 #[rstest]
912 #[case(0, 0.0)]
913 #[case(1, 1.0)]
914 #[case(1, 1.1)]
915 #[case(9, 0.000_000_001)]
916 #[case(16, 0.000_000_000_000_000_1)]
917 fn test_f64_to_fixed_u128_to_fixed(#[case] precision: u8, #[case] value: f64) {
918 let fixed = f64_to_fixed_u128(value, precision);
919 let result = fixed_u128_to_f64(fixed);
920 assert_eq!(result, value);
921 }
922
923 #[rstest]
924 #[case(0, 123_456.0)]
925 #[case(0, 123_456.7)]
926 #[case(0, 123_456.4)]
927 #[case(1, 123_456.0)]
928 #[case(1, 123_456.7)]
929 #[case(1, 123_456.4)]
930 #[case(2, 123_456.0)]
931 #[case(2, 123_456.7)]
932 #[case(2, 123_456.4)]
933 fn test_f64_to_fixed_i128_with_precision(#[case] precision: u8, #[case] value: f64) {
934 let result = f64_to_fixed_i128(value, precision);
935
936 let pow1 = 10_i128.pow(u32::from(precision));
938 let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
939 let rounded = (value * pow1 as f64).round() as i128;
940 let expected = rounded * pow2;
941
942 assert_eq!(
943 result, expected,
944 "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
945 );
946 }
947
948 #[rstest]
949 #[case(0, 5.555_555_555_555_555)]
950 #[case(1, 5.555_555_555_555_555)]
951 #[case(2, 5.555_555_555_555_555)]
952 #[case(3, 5.555_555_555_555_555)]
953 #[case(4, 5.555_555_555_555_555)]
954 #[case(5, 5.555_555_555_555_555)]
955 #[case(6, 5.555_555_555_555_555)]
956 #[case(7, 5.555_555_555_555_555)]
957 #[case(8, 5.555_555_555_555_555)]
958 #[case(9, 5.555_555_555_555_555)]
959 #[case(10, 5.555_555_555_555_555)]
960 #[case(11, 5.555_555_555_555_555)]
961 #[case(12, 5.555_555_555_555_555)]
962 #[case(13, 5.555_555_555_555_555)]
963 #[case(14, 5.555_555_555_555_555)]
964 #[case(15, 5.555_555_555_555_555)]
965 #[case(0, -5.555_555_555_555_555)]
966 #[case(1, -5.555_555_555_555_555)]
967 #[case(2, -5.555_555_555_555_555)]
968 #[case(3, -5.555_555_555_555_555)]
969 #[case(4, -5.555_555_555_555_555)]
970 #[case(5, -5.555_555_555_555_555)]
971 #[case(6, -5.555_555_555_555_555)]
972 #[case(7, -5.555_555_555_555_555)]
973 #[case(8, -5.555_555_555_555_555)]
974 #[case(9, -5.555_555_555_555_555)]
975 #[case(10, -5.555_555_555_555_555)]
976 #[case(11, -5.555_555_555_555_555)]
977 #[case(12, -5.555_555_555_555_555)]
978 #[case(13, -5.555_555_555_555_555)]
979 #[case(14, -5.555_555_555_555_555)]
980 #[case(15, -5.555_555_555_555_555)]
981 fn test_f64_to_fixed_i128(#[case] precision: u8, #[case] value: f64) {
982 if precision > FIXED_PRECISION {
984 return;
985 }
986
987 let result = f64_to_fixed_i128(value, precision);
988
989 let pow1 = 10_i128.pow(u32::from(precision));
991 let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
992 let rounded = (value * pow1 as f64).round() as i128;
993 let expected = rounded * pow2;
994
995 assert_eq!(
996 result, expected,
997 "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
998 );
999 }
1000
1001 #[rstest]
1002 #[case(0, 5.555_555_555_555_555)]
1003 #[case(1, 5.555_555_555_555_555)]
1004 #[case(2, 5.555_555_555_555_555)]
1005 #[case(3, 5.555_555_555_555_555)]
1006 #[case(4, 5.555_555_555_555_555)]
1007 #[case(5, 5.555_555_555_555_555)]
1008 #[case(6, 5.555_555_555_555_555)]
1009 #[case(7, 5.555_555_555_555_555)]
1010 #[case(8, 5.555_555_555_555_555)]
1011 #[case(9, 5.555_555_555_555_555)]
1012 #[case(10, 5.555_555_555_555_555)]
1013 #[case(11, 5.555_555_555_555_555)]
1014 #[case(12, 5.555_555_555_555_555)]
1015 #[case(13, 5.555_555_555_555_555)]
1016 #[case(14, 5.555_555_555_555_555)]
1017 #[case(15, 5.555_555_555_555_555)]
1018 #[case(16, 5.555_555_555_555_555)]
1019 fn test_f64_to_fixed_u64(#[case] precision: u8, #[case] value: f64) {
1020 if precision > FIXED_PRECISION {
1022 return;
1023 }
1024
1025 let result = f64_to_fixed_u128(value, precision);
1026
1027 let pow1 = 10_u128.pow(u32::from(precision));
1029 let pow2 = 10_u128.pow(u32::from(FIXED_PRECISION - precision));
1030 let rounded = (value * pow1 as f64).round() as u128;
1031 let expected = rounded * pow2;
1032
1033 assert_eq!(
1034 result, expected,
1035 "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
1036 );
1037 }
1038
1039 #[rstest]
1040 fn test_fixed_i128_to_f64(
1041 #[values(1, -1, 2, -2, 10, -10, 100, -100, 1_000, -1_000, -10_000, -100_000)] value: i128,
1042 ) {
1043 assert_eq!(fixed_i128_to_f64(value), value as f64 / FIXED_SCALAR);
1044 }
1045
1046 #[rstest]
1047 fn test_fixed_u128_to_f64(
1048 #[values(
1049 0,
1050 1,
1051 2,
1052 3,
1053 10,
1054 100,
1055 1_000,
1056 10_000,
1057 100_000,
1058 1_000_000,
1059 10_000_000,
1060 100_000_000,
1061 1_000_000_000,
1062 10_000_000_000,
1063 100_000_000_000,
1064 1_000_000_000_000,
1065 10_000_000_000_000,
1066 100_000_000_000_000,
1067 1_000_000_000_000_000,
1068 10_000_000_000_000_000,
1069 100_000_000_000_000_000,
1070 1_000_000_000_000_000_000,
1071 10_000_000_000_000_000_000,
1072 100_000_000_000_000_000_000
1073 )]
1074 value: u128,
1075 ) {
1076 let result = fixed_u128_to_f64(value);
1077 assert_eq!(result, (value as f64) / FIXED_SCALAR);
1078 }
1079
1080 #[rstest]
1085 #[case(0, 0)] #[case(0, 10_000_000_000_000_000)] #[case(0, 1_200_000_000_000_000_000)] #[case(8, 12_345_678_900_000_000)] #[case(15, 1_234_567_890_123_450)] fn test_check_fixed_raw_u128_valid(#[case] precision: u8, #[case] raw: u128) {
1091 assert!(check_fixed_raw_u128(raw, precision).is_ok());
1092 }
1093
1094 #[rstest]
1095 #[case(0, 1)] #[case(0, 9_999_999_999_999_999)] #[case(0, 10_000_000_000_000_001)] #[case(8, 12_345_678_900_000_001)] #[case(15, 1_234_567_890_123_451)] fn test_check_fixed_raw_u128_invalid(#[case] precision: u8, #[case] raw: u128) {
1101 assert!(check_fixed_raw_u128(raw, precision).is_err());
1102 }
1103
1104 #[rstest]
1105 fn test_check_fixed_raw_u128_at_max_precision() {
1106 assert!(check_fixed_raw_u128(0, FIXED_PRECISION).is_ok());
1108 assert!(check_fixed_raw_u128(1, FIXED_PRECISION).is_ok());
1109 assert!(check_fixed_raw_u128(123_456_789, FIXED_PRECISION).is_ok());
1110 assert!(check_fixed_raw_u128(u128::MAX, FIXED_PRECISION).is_ok());
1111 }
1112
1113 #[rstest]
1114 #[case(0, 0)]
1115 #[case(0, 10_000_000_000_000_000)]
1116 #[case(0, -10_000_000_000_000_000)]
1117 #[case(8, 12_345_678_900_000_000)]
1118 #[case(8, -12_345_678_900_000_000)]
1119 fn test_check_fixed_raw_i128_valid(#[case] precision: u8, #[case] raw: i128) {
1120 assert!(check_fixed_raw_i128(raw, precision).is_ok());
1121 }
1122
1123 #[rstest]
1124 #[case(0, 1)]
1125 #[case(0, -1)]
1126 #[case(0, 9_999_999_999_999_999)]
1127 #[case(0, -9_999_999_999_999_999)]
1128 fn test_check_fixed_raw_i128_invalid(#[case] precision: u8, #[case] raw: i128) {
1129 assert!(check_fixed_raw_i128(raw, precision).is_err());
1130 }
1131
1132 #[rstest]
1133 fn test_check_fixed_raw_i128_at_max_precision() {
1134 assert!(check_fixed_raw_i128(0, FIXED_PRECISION).is_ok());
1135 assert!(check_fixed_raw_i128(1, FIXED_PRECISION).is_ok());
1136 assert!(check_fixed_raw_i128(-1, FIXED_PRECISION).is_ok());
1137 assert!(check_fixed_raw_i128(i128::MAX, FIXED_PRECISION).is_ok());
1138 assert!(check_fixed_raw_i128(i128::MIN, FIXED_PRECISION).is_ok());
1139 }
1140}
1141
1142#[cfg(not(feature = "high-precision"))]
1143#[cfg(test)]
1144mod tests {
1145 use nautilus_core::approx_eq;
1146 use rstest::rstest;
1147
1148 use super::*;
1149
1150 #[rstest]
1151 fn test_precision_boundaries() {
1152 assert!(check_fixed_precision(0).is_ok());
1153 assert!(check_fixed_precision(FIXED_PRECISION).is_ok());
1154 assert!(check_fixed_precision(FIXED_PRECISION + 1).is_err());
1155 }
1156
1157 #[rstest]
1158 #[case(0.0)]
1159 #[case(1.0)]
1160 #[case(-1.0)]
1161 fn test_basic_roundtrip(#[case] value: f64) {
1162 for precision in 0..=FIXED_PRECISION {
1163 let fixed = f64_to_fixed_i64(value, precision);
1164 let result = fixed_i64_to_f64(fixed);
1165 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
1166 }
1167 }
1168
1169 #[rstest]
1170 #[case(1000000.0)]
1171 #[case(-1000000.0)]
1172 fn test_large_value_roundtrip(#[case] value: f64) {
1173 for precision in 0..=FIXED_PRECISION {
1174 let fixed = f64_to_fixed_i64(value, precision);
1175 let result = fixed_i64_to_f64(fixed);
1176 assert!(approx_eq!(f64, value, result, epsilon = 0.000_1));
1177 }
1178 }
1179
1180 #[rstest]
1181 #[case(0, 123456.0, 123_456_000_000_000)]
1182 #[case(0, 123456.7, 123_457_000_000_000)]
1183 #[case(1, 123456.7, 123_456_700_000_000)]
1184 #[case(2, 123456.78, 123_456_780_000_000)]
1185 #[case(8, 123456.123_456_78, 123_456_123_456_780)]
1186 #[case(9, 123456.123_456_789, 123_456_123_456_789)]
1187 fn test_precision_specific_values(
1188 #[case] precision: u8,
1189 #[case] value: f64,
1190 #[case] expected: i64,
1191 ) {
1192 assert_eq!(f64_to_fixed_i64(value, precision), expected);
1193 }
1194
1195 #[rstest]
1196 #[case(0.0)]
1197 #[case(1.0)]
1198 #[case(1000000.0)]
1199 fn test_unsigned_basic_roundtrip(#[case] value: f64) {
1200 for precision in 0..=FIXED_PRECISION {
1201 let fixed = f64_to_fixed_u64(value, precision);
1202 let result = fixed_u64_to_f64(fixed);
1203 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
1204 }
1205 }
1206
1207 #[rstest]
1208 #[case(0, 1.4, 1.0)]
1209 #[case(0, 1.5, 2.0)]
1210 #[case(0, 1.6, 2.0)]
1211 #[case(1, 1.44, 1.4)]
1212 #[case(1, 1.45, 1.5)]
1213 #[case(1, 1.46, 1.5)]
1214 #[case(2, 1.444, 1.44)]
1215 #[case(2, 1.445, 1.45)]
1216 #[case(2, 1.446, 1.45)]
1217 fn test_rounding(#[case] precision: u8, #[case] input: f64, #[case] expected: f64) {
1218 let fixed = f64_to_fixed_i128(input, precision);
1219 assert!(approx_eq!(
1220 f64,
1221 fixed_i128_to_f64(fixed),
1222 expected,
1223 epsilon = 0.000_000_001
1224 ));
1225 }
1226
1227 #[rstest]
1228 fn test_special_values() {
1229 assert_eq!(f64_to_fixed_i128(0.0, FIXED_PRECISION), 0);
1231 assert_eq!(f64_to_fixed_i128(-0.0, FIXED_PRECISION), 0);
1232
1233 let smallest_positive = 1.0 / FIXED_SCALAR;
1235 let fixed_smallest = f64_to_fixed_i128(smallest_positive, FIXED_PRECISION);
1236 assert_eq!(fixed_smallest, 1);
1237
1238 let large_int = 1_000_000_000.0;
1240 let fixed_large = f64_to_fixed_i128(large_int, 0);
1241 assert_eq!(fixed_i128_to_f64(fixed_large), large_int);
1242 }
1243
1244 #[rstest]
1245 #[case(0)]
1246 #[case(FIXED_PRECISION)]
1247 fn test_valid_precision(#[case] precision: u8) {
1248 let result = check_fixed_precision(precision);
1249 assert!(result.is_ok());
1250 }
1251
1252 #[rstest]
1253 fn test_invalid_precision() {
1254 let precision = FIXED_PRECISION + 1;
1255 let result = check_fixed_precision(precision);
1256 assert!(result.is_err());
1257 }
1258
1259 #[rstest]
1260 #[case(0, 0.0)]
1261 #[case(1, 1.0)]
1262 #[case(1, 1.1)]
1263 #[case(9, 0.000_000_001)]
1264 #[case(0, -0.0)]
1265 #[case(1, -1.0)]
1266 #[case(1, -1.1)]
1267 #[case(9, -0.000_000_001)]
1268 fn test_f64_to_fixed_i64_to_fixed(#[case] precision: u8, #[case] value: f64) {
1269 let fixed = f64_to_fixed_i64(value, precision);
1270 let result = fixed_i64_to_f64(fixed);
1271 assert_eq!(result, value);
1272 }
1273
1274 #[rstest]
1275 #[case(0, 0.0)]
1276 #[case(1, 1.0)]
1277 #[case(1, 1.1)]
1278 #[case(9, 0.000_000_001)]
1279 fn test_f64_to_fixed_u64_to_fixed(#[case] precision: u8, #[case] value: f64) {
1280 let fixed = f64_to_fixed_u64(value, precision);
1281 let result = fixed_u64_to_f64(fixed);
1282 assert_eq!(result, value);
1283 }
1284
1285 #[rstest]
1286 #[case(0, 123456.0, 123_456_000_000_000)]
1287 #[case(0, 123456.7, 123_457_000_000_000)]
1288 #[case(0, 123_456.4, 123_456_000_000_000)]
1289 #[case(1, 123456.0, 123_456_000_000_000)]
1290 #[case(1, 123456.7, 123_456_700_000_000)]
1291 #[case(1, 123_456.4, 123_456_400_000_000)]
1292 #[case(2, 123456.0, 123_456_000_000_000)]
1293 #[case(2, 123456.7, 123_456_700_000_000)]
1294 #[case(2, 123_456.4, 123_456_400_000_000)]
1295 fn test_f64_to_fixed_i64_with_precision(
1296 #[case] precision: u8,
1297 #[case] value: f64,
1298 #[case] expected: i64,
1299 ) {
1300 assert_eq!(f64_to_fixed_i64(value, precision), expected);
1301 }
1302
1303 #[rstest]
1304 #[case(0, 5.5, 6_000_000_000)]
1305 #[case(1, 5.55, 5_600_000_000)]
1306 #[case(2, 5.555, 5_560_000_000)]
1307 #[case(3, 5.5555, 5_556_000_000)]
1308 #[case(4, 5.55555, 5_555_600_000)]
1309 #[case(5, 5.555_555, 5_555_560_000)]
1310 #[case(6, 5.555_555_5, 5_555_556_000)]
1311 #[case(7, 5.555_555_55, 5_555_555_600)]
1312 #[case(8, 5.555_555_555, 5_555_555_560)]
1313 #[case(9, 5.555_555_555_5, 5_555_555_556)]
1314 #[case(0, -5.5, -6_000_000_000)]
1315 #[case(1, -5.55, -5_600_000_000)]
1316 #[case(2, -5.555, -5_560_000_000)]
1317 #[case(3, -5.5555, -5_556_000_000)]
1318 #[case(4, -5.55555, -5_555_600_000)]
1319 #[case(5, -5.555_555, -5_555_560_000)]
1320 #[case(6, -5.555_555_5, -5_555_556_000)]
1321 #[case(7, -5.555_555_55, -5_555_555_600)]
1322 #[case(8, -5.555_555_555, -5_555_555_560)]
1323 #[case(9, -5.555_555_555_5, -5_555_555_556)]
1324 fn test_f64_to_fixed_i64(#[case] precision: u8, #[case] value: f64, #[case] expected: i64) {
1325 assert_eq!(f64_to_fixed_i64(value, precision), expected);
1326 }
1327
1328 #[rstest]
1329 #[case(0, 5.5, 6_000_000_000)]
1330 #[case(1, 5.55, 5_600_000_000)]
1331 #[case(2, 5.555, 5_560_000_000)]
1332 #[case(3, 5.5555, 5_556_000_000)]
1333 #[case(4, 5.55555, 5_555_600_000)]
1334 #[case(5, 5.555_555, 5_555_560_000)]
1335 #[case(6, 5.555_555_5, 5_555_556_000)]
1336 #[case(7, 5.555_555_55, 5_555_555_600)]
1337 #[case(8, 5.555_555_555, 5_555_555_560)]
1338 #[case(9, 5.555_555_555_5, 5_555_555_556)]
1339 fn test_f64_to_fixed_u64(#[case] precision: u8, #[case] value: f64, #[case] expected: u64) {
1340 assert_eq!(f64_to_fixed_u64(value, precision), expected);
1341 }
1342
1343 #[rstest]
1344 fn test_fixed_i64_to_f64(
1345 #[values(1, -1, 2, -2, 10, -10, 100, -100, 1_000, -1_000)] value: i64,
1346 ) {
1347 assert_eq!(fixed_i64_to_f64(value), value as f64 / FIXED_SCALAR);
1348 }
1349
1350 #[rstest]
1351 fn test_fixed_u64_to_f64(
1352 #[values(
1353 0,
1354 1,
1355 2,
1356 3,
1357 10,
1358 100,
1359 1_000,
1360 10_000,
1361 100_000,
1362 1_000_000,
1363 10_000_000,
1364 100_000_000,
1365 1_000_000_000,
1366 10_000_000_000,
1367 100_000_000_000,
1368 1_000_000_000_000,
1369 10_000_000_000_000,
1370 100_000_000_000_000,
1371 1_000_000_000_000_000
1372 )]
1373 value: u64,
1374 ) {
1375 let result = fixed_u64_to_f64(value);
1376 assert_eq!(result, (value as f64) / FIXED_SCALAR);
1377 }
1378
1379 #[rstest]
1380 #[case(0, 0)] #[case(0, 1_000_000_000)] #[case(0, 120_000_000_000)] #[case(2, 123_450_000_000)] #[case(8, 1_234_567_890)] fn test_check_fixed_raw_u64_valid(#[case] precision: u8, #[case] raw: u64) {
1386 assert!(check_fixed_raw_u64(raw, precision).is_ok());
1387 }
1388
1389 #[rstest]
1390 #[case(0, 1)] #[case(0, 999_999_999)] #[case(0, 1_000_000_001)] #[case(0, 119_582_001_968_421_736)] #[case(2, 123_456_789_000)] #[case(8, 1_234_567_891)] fn test_check_fixed_raw_u64_invalid(#[case] precision: u8, #[case] raw: u64) {
1397 assert!(check_fixed_raw_u64(raw, precision).is_err());
1398 }
1399
1400 #[rstest]
1401 fn test_check_fixed_raw_u64_at_max_precision() {
1402 assert!(check_fixed_raw_u64(0, FIXED_PRECISION).is_ok());
1404 assert!(check_fixed_raw_u64(1, FIXED_PRECISION).is_ok());
1405 assert!(check_fixed_raw_u64(123_456_789, FIXED_PRECISION).is_ok());
1406 assert!(check_fixed_raw_u64(u64::MAX, FIXED_PRECISION).is_ok());
1407 }
1408
1409 #[rstest]
1410 #[case(0, 0)]
1411 #[case(0, 1_000_000_000)]
1412 #[case(0, -1_000_000_000)]
1413 #[case(2, 123_450_000_000)]
1414 #[case(2, -123_450_000_000)]
1415 fn test_check_fixed_raw_i64_valid(#[case] precision: u8, #[case] raw: i64) {
1416 assert!(check_fixed_raw_i64(raw, precision).is_ok());
1417 }
1418
1419 #[rstest]
1420 #[case(0, 1)]
1421 #[case(0, -1)]
1422 #[case(0, 999_999_999)]
1423 #[case(0, -999_999_999)]
1424 fn test_check_fixed_raw_i64_invalid(#[case] precision: u8, #[case] raw: i64) {
1425 assert!(check_fixed_raw_i64(raw, precision).is_err());
1426 }
1427
1428 #[rstest]
1429 fn test_check_fixed_raw_i64_at_max_precision() {
1430 assert!(check_fixed_raw_i64(0, FIXED_PRECISION).is_ok());
1431 assert!(check_fixed_raw_i64(1, FIXED_PRECISION).is_ok());
1432 assert!(check_fixed_raw_i64(-1, FIXED_PRECISION).is_ok());
1433 assert!(check_fixed_raw_i64(i64::MAX, FIXED_PRECISION).is_ok());
1434 assert!(check_fixed_raw_i64(i64::MIN, FIXED_PRECISION).is_ok());
1435 }
1436}
1437
1438#[cfg(test)]
1439mod bankers_round_tests {
1440 use std::str::FromStr;
1441
1442 use rstest::rstest;
1443 use rust_decimal::{Decimal, RoundingStrategy};
1444
1445 use super::*;
1446
1447 #[rstest]
1448 #[case(0, 0, 0)]
1450 #[case(1, 0, 1)]
1451 #[case(5, 0, 5)]
1452 #[case(99, 0, 99)]
1453 #[case(-7, 0, -7)]
1454 #[case(12345, 39, 0)]
1456 #[case(i128::from(i64::MAX), 100, 0)]
1457 #[case(-99999, 50, 0)]
1458 #[case(15, 1, 2)] #[case(25, 1, 2)] #[case(35, 1, 4)] #[case(45, 1, 4)] #[case(55, 1, 6)] #[case(65, 1, 6)] #[case(75, 1, 8)] #[case(85, 1, 8)] #[case(95, 1, 10)] #[case(105, 1, 10)] #[case(14, 1, 1)] #[case(16, 1, 2)] #[case(24, 1, 2)] #[case(26, 1, 3)] #[case(11, 1, 1)] #[case(19, 1, 2)] #[case(150, 2, 2)] #[case(250, 2, 2)] #[case(350, 2, 4)] #[case(450, 2, 4)] #[case(550, 2, 6)] #[case(1050, 2, 10)] #[case(1150, 2, 12)] #[case(149, 2, 1)] #[case(151, 2, 2)] #[case(199, 2, 2)] #[case(101, 2, 1)] #[case(1500, 3, 2)] #[case(2500, 3, 2)] #[case(3500, 3, 4)] #[case(10500, 3, 10)] #[case(11500, 3, 12)] #[case(1499, 3, 1)] #[case(1501, 3, 2)] #[case(-15, 1, -2)] #[case(-25, 1, -2)] #[case(-35, 1, -4)] #[case(-45, 1, -4)] #[case(-55, 1, -6)] #[case(-65, 1, -6)] #[case(-150, 2, -2)] #[case(-250, 2, -2)] #[case(-350, 2, -4)] #[case(-14, 1, -1)] #[case(-16, 1, -2)] #[case(-24, 1, -2)] #[case(-26, 1, -3)] #[case(0, 1, 0)]
1515 #[case(0, 2, 0)]
1516 #[case(0, 5, 0)]
1517 #[case(123_456_789, 3, 123_457)] #[case(123_456_500, 3, 123_456)] #[case(123_457_500, 3, 123_458)] #[case(100_005, 1, 10_000)] #[case(100_015, 1, 10_002)] #[case(999_999_999_999_999_995, 1, 100_000_000_000_000_000)]
1525 #[case(1_000_000_000_000_000_005, 1, 100_000_000_000_000_000)]
1526 fn test_bankers_round(#[case] mantissa: i128, #[case] excess: u32, #[case] expected: i128) {
1527 assert_eq!(
1528 bankers_round(mantissa, excess),
1529 expected,
1530 "bankers_round({mantissa}, {excess}) expected {expected}"
1531 );
1532 }
1533
1534 #[rstest]
1536 #[case(15, 1)]
1537 #[case(25, 1)]
1538 #[case(35, 1)]
1539 #[case(150, 2)]
1540 #[case(250, 2)]
1541 #[case(1500, 3)]
1542 #[case(2500, 3)]
1543 #[case(123_456_789, 3)]
1544 #[case(14, 1)]
1545 #[case(16, 1)]
1546 fn test_bankers_round_negative_symmetry(#[case] mantissa: i128, #[case] excess: u32) {
1547 assert_eq!(
1548 bankers_round(-mantissa, excess),
1549 -bankers_round(mantissa, excess),
1550 "Negative symmetry failed for mantissa={mantissa}, excess={excess}"
1551 );
1552 }
1553
1554 #[rstest]
1556 #[case("1.005", 2, "1.00")] #[case("1.015", 2, "1.02")] #[case("1.025", 2, "1.02")] #[case("1.035", 2, "1.04")] #[case("1.045", 2, "1.04")] #[case("2.5", 0, "2")] #[case("3.5", 0, "4")] #[case("-2.5", 0, "-2")]
1564 #[case("-3.5", 0, "-4")]
1565 #[case("123.456", 2, "123.46")]
1566 #[case("123.455", 2, "123.46")] #[case("123.445", 2, "123.44")] fn test_bankers_round_matches_decimal(
1569 #[case] input: &str,
1570 #[case] target_precision: u8,
1571 #[case] expected: &str,
1572 ) {
1573 let dec = Decimal::from_str(input).unwrap();
1574 let expected_dec = Decimal::from_str(expected).unwrap();
1575
1576 let decimal_rounded = dec.round_dp_with_strategy(
1577 u32::from(target_precision),
1578 RoundingStrategy::MidpointNearestEven,
1579 );
1580 assert_eq!(
1581 decimal_rounded, expected_dec,
1582 "Decimal rounding sanity check failed for {input}"
1583 );
1584
1585 let mantissa = dec.mantissa();
1586 let scale = dec.scale() as u8;
1587 let excess = u32::from(scale.saturating_sub(target_precision));
1588 if excess > 0 {
1589 let rounded = bankers_round(mantissa, excess);
1590
1591 let expected_mantissa = expected_dec.mantissa();
1593 let expected_scale = expected_dec.scale() as u8;
1594 let scale_diff = u32::from(target_precision.saturating_sub(expected_scale));
1595 let normalized_expected = expected_mantissa * 10i128.pow(scale_diff);
1596
1597 assert_eq!(
1598 rounded, normalized_expected,
1599 "bankers_round disagrees with Decimal for {input} at precision {target_precision}"
1600 );
1601 }
1602 }
1603}