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].cast_signed();
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]
434pub fn correct_raw_u128(raw: u128, precision: u8) -> u128 {
435 if precision >= FIXED_PRECISION {
436 return raw;
437 }
438 let exp = usize::from(FIXED_PRECISION - precision);
439 let scale = u128::from(POWERS_OF_10[exp]);
440 let half_scale = scale / 2;
441 let remainder = raw % scale;
442 if remainder == 0 {
443 raw
444 } else if remainder >= half_scale {
445 raw.checked_add(scale - remainder)
446 .unwrap_or(raw - remainder)
447 } else {
448 raw - remainder
449 }
450}
451
452#[must_use]
460pub fn correct_raw_u64(raw: u64, precision: u8) -> u64 {
461 if precision >= FIXED_PRECISION {
462 return raw;
463 }
464 let exp = usize::from(FIXED_PRECISION - precision);
465 let scale = POWERS_OF_10[exp];
466 let half_scale = scale / 2;
467 let remainder = raw % scale;
468 if remainder == 0 {
469 raw
470 } else if remainder >= half_scale {
471 raw.checked_add(scale - remainder)
472 .unwrap_or(raw - remainder)
473 } else {
474 raw - remainder
475 }
476}
477
478#[must_use]
486pub fn correct_raw_i128(raw: i128, precision: u8) -> i128 {
487 if precision >= FIXED_PRECISION {
488 return raw;
489 }
490 let exp = usize::from(FIXED_PRECISION - precision);
491 let scale = i128::from(POWERS_OF_10[exp]);
492 let half_scale = scale / 2;
493 let remainder = raw % scale;
494 if remainder == 0 {
495 raw
496 } else if raw >= 0 {
497 if remainder >= half_scale {
498 raw.checked_add(scale - remainder)
499 .unwrap_or(raw - remainder)
500 } else {
501 raw - remainder
502 }
503 } else {
504 if remainder.abs() >= half_scale {
506 raw.checked_sub(scale + remainder)
507 .unwrap_or(raw - remainder)
508 } else {
509 raw - remainder
510 }
511 }
512}
513
514#[must_use]
522pub fn correct_raw_i64(raw: i64, precision: u8) -> i64 {
523 if precision >= FIXED_PRECISION {
524 return raw;
525 }
526 let exp = usize::from(FIXED_PRECISION - precision);
527 let scale = POWERS_OF_10[exp].cast_signed();
528 let half_scale = scale / 2;
529 let remainder = raw % scale;
530 if remainder == 0 {
531 raw
532 } else if raw >= 0 {
533 if remainder >= half_scale {
534 raw.checked_add(scale - remainder)
535 .unwrap_or(raw - remainder)
536 } else {
537 raw - remainder
538 }
539 } else {
540 if remainder.abs() >= half_scale {
542 raw.checked_sub(scale + remainder)
543 .unwrap_or(raw - remainder)
544 } else {
545 raw - remainder
546 }
547 }
548}
549
550#[must_use]
556#[inline]
557pub fn correct_price_raw(raw: PriceRaw, precision: u8) -> PriceRaw {
558 #[cfg(feature = "high-precision")]
559 {
560 correct_raw_i128(raw, precision)
561 }
562 #[cfg(not(feature = "high-precision"))]
563 {
564 correct_raw_i64(raw, precision)
565 }
566}
567
568#[must_use]
574#[inline]
575pub fn correct_quantity_raw(raw: QuantityRaw, precision: u8) -> QuantityRaw {
576 #[cfg(feature = "high-precision")]
577 {
578 correct_raw_u128(raw, precision)
579 }
580 #[cfg(not(feature = "high-precision"))]
581 {
582 correct_raw_u64(raw, precision)
583 }
584}
585
586#[must_use]
591#[inline]
592pub fn bankers_round(mantissa: i128, excess: u32) -> i128 {
593 if excess == 0 {
594 return mantissa;
595 }
596
597 if excess >= 39 {
599 return 0;
600 }
601
602 let divisor = 10i128.pow(excess);
603 let quotient = mantissa / divisor;
604 let remainder = mantissa % divisor;
605 let half = divisor / 2;
606
607 if remainder.abs() > half || (remainder.abs() == half && quotient % 2 != 0) {
608 quotient + mantissa.signum()
609 } else {
610 quotient
611 }
612}
613
614pub fn mantissa_exponent_to_fixed_i128(
629 mantissa: i128,
630 exponent: i8,
631 precision: u8,
632) -> CorrectnessResult<i128> {
633 check_fixed_precision(precision)?;
634
635 let precision_i16 = i16::from(precision);
636 let target_scale = i16::from(FIXED_PRECISION).max(precision_i16);
637 let frac_digits = -i16::from(exponent);
638
639 let mantissa = if frac_digits > precision_i16 {
640 let excess = u32::from((frac_digits - precision_i16).cast_unsigned());
641 bankers_round(mantissa, excess)
642 } else {
643 mantissa
644 };
645
646 let scale_after_rounding = frac_digits.min(precision_i16);
647 let scale_exp = target_scale - scale_after_rounding;
648 if scale_exp > 38 {
649 return Err(CorrectnessError::PredicateViolation {
650 message: format!(
651 "Exponent {exponent} produces scale factor 10^{scale_exp} which exceeds i128 range"
652 ),
653 });
654 }
655
656 if scale_exp >= 0 {
657 mantissa.checked_mul(10i128.pow(u32::from(scale_exp.cast_unsigned())))
658 } else {
659 Some(mantissa / 10i128.pow(u32::from((-scale_exp).cast_unsigned())))
660 }
661 .ok_or_else(|| CorrectnessError::PredicateViolation {
662 message: "Overflow when scaling mantissa to fixed precision".to_string(),
663 })
664}
665
666pub(crate) fn mantissa_exponent_to_raw_checked<R>(
667 mantissa: i128,
668 exponent: i8,
669 precision: u8,
670 context: &'static str,
671 raw_type_name: &'static str,
672 value_type_name: &'static str,
673) -> CorrectnessResult<R>
674where
675 R: TryFrom<i128>,
676{
677 check_fixed_precision(precision)?;
678
679 let raw_i128 = if mantissa == 0 {
680 0
681 } else {
682 mantissa_exponent_to_fixed_i128(mantissa, exponent, precision).map_err(|_| {
683 CorrectnessError::PredicateViolation {
684 message: format!(
685 "Overflow in {context} (mantissa={mantissa}, exponent={exponent}, precision={precision})"
686 ),
687 }
688 })?
689 };
690
691 raw_i128
692 .try_into()
693 .map_err(|_| CorrectnessError::PredicateViolation {
694 message: format!(
695 "Raw value {raw_i128} exceeds {raw_type_name} range for {value_type_name}"
696 ),
697 })
698}
699
700#[must_use]
717#[expect(
718 clippy::cast_precision_loss,
719 clippy::cast_possible_truncation,
720 reason = "f64 to fixed-point conversion is inherently lossy; callers validate range and finiteness"
721)]
722pub fn f64_to_fixed_i64(value: f64, precision: u8) -> i64 {
723 check_fixed_precision(precision).expect_display(FAILED);
724 let pow1 = 10_i64.pow(u32::from(precision));
725 let pow2 = 10_i64.pow(u32::from(FIXED_PRECISION - precision));
726 let rounded = (value * pow1 as f64).round() as i64;
727 rounded
728 .checked_mul(pow2)
729 .expect("Overflow when scaling f64 to fixed-point i64")
730}
731
732#[must_use]
742#[expect(
743 clippy::cast_precision_loss,
744 clippy::cast_possible_truncation,
745 reason = "f64 to fixed-point conversion is inherently lossy; callers validate range and finiteness"
746)]
747pub fn f64_to_fixed_i128(value: f64, precision: u8) -> i128 {
748 check_fixed_precision(precision).expect_display(FAILED);
749 let pow1 = 10_i128.pow(u32::from(precision));
750 let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
751 let rounded = (value * pow1 as f64).round() as i128;
752 rounded
753 .checked_mul(pow2)
754 .expect("Overflow when scaling f64 to fixed-point i128")
755}
756
757#[must_use]
767#[expect(
768 clippy::cast_precision_loss,
769 clippy::cast_possible_truncation,
770 clippy::cast_sign_loss,
771 reason = "f64 to fixed-point conversion is inherently lossy; callers validate range and finiteness"
772)]
773pub fn f64_to_fixed_u64(value: f64, precision: u8) -> u64 {
774 check_fixed_precision(precision).expect_display(FAILED);
775 let pow1 = 10_u64.pow(u32::from(precision));
776 let pow2 = 10_u64.pow(u32::from(FIXED_PRECISION - precision));
777 let rounded = (value * pow1 as f64).round() as u64;
778 rounded
779 .checked_mul(pow2)
780 .expect("Overflow when scaling f64 to fixed-point u64")
781}
782
783#[must_use]
793#[expect(
794 clippy::cast_precision_loss,
795 clippy::cast_possible_truncation,
796 clippy::cast_sign_loss,
797 reason = "f64 to fixed-point conversion is inherently lossy; callers validate range and finiteness"
798)]
799pub fn f64_to_fixed_u128(value: f64, precision: u8) -> u128 {
800 check_fixed_precision(precision).expect_display(FAILED);
801 let pow1 = 10_u128.pow(u32::from(precision));
802 let pow2 = 10_u128.pow(u32::from(FIXED_PRECISION - precision));
803 let rounded = (value * pow1 as f64).round() as u128;
804 rounded
805 .checked_mul(pow2)
806 .expect("Overflow when scaling f64 to fixed-point u128")
807}
808
809#[must_use]
811#[expect(
812 clippy::cast_precision_loss,
813 reason = "i64 to f64 is inherently lossy above 2^53; accepted for float interop"
814)]
815pub fn fixed_i64_to_f64(value: i64) -> f64 {
816 (value as f64) / FIXED_SCALAR
817}
818
819#[must_use]
821#[expect(
822 clippy::cast_precision_loss,
823 reason = "i128 to f64 is inherently lossy above 2^53; accepted for float interop"
824)]
825pub fn fixed_i128_to_f64(value: i128) -> f64 {
826 (value as f64) / FIXED_SCALAR
827}
828
829#[must_use]
831#[expect(
832 clippy::cast_precision_loss,
833 reason = "u64 to f64 is inherently lossy above 2^53; accepted for float interop"
834)]
835pub fn fixed_u64_to_f64(value: u64) -> f64 {
836 (value as f64) / FIXED_SCALAR
837}
838
839#[must_use]
841#[expect(
842 clippy::cast_precision_loss,
843 reason = "u128 to f64 is inherently lossy above 2^53; accepted for float interop"
844)]
845pub fn fixed_u128_to_f64(value: u128) -> f64 {
846 (value as f64) / FIXED_SCALAR
847}
848
849#[cfg(feature = "high-precision")]
850#[cfg(test)]
851mod tests {
852 use nautilus_core::approx_eq;
853 use rstest::rstest;
854
855 use super::*;
856
857 #[cfg(not(feature = "defi"))]
858 #[rstest]
859 fn test_precision_boundaries() {
860 assert!(check_fixed_precision(0).is_ok());
861 assert!(check_fixed_precision(FIXED_PRECISION).is_ok());
862 assert!(check_fixed_precision(FIXED_PRECISION + 1).is_err());
863 }
864
865 #[cfg(feature = "defi")]
866 #[rstest]
867 fn test_precision_boundaries() {
868 use crate::defi::WEI_PRECISION;
869
870 assert!(check_fixed_precision(0).is_ok());
871 assert!(check_fixed_precision(WEI_PRECISION).is_ok());
872 assert!(check_fixed_precision(WEI_PRECISION + 1).is_err());
873 }
874
875 #[rstest]
876 #[case(0.0)]
877 #[case(1.0)]
878 #[case(-1.0)]
879 fn test_basic_roundtrip(#[case] value: f64) {
880 for precision in 0..=FIXED_PRECISION {
881 let fixed = f64_to_fixed_i128(value, precision);
882 let result = fixed_i128_to_f64(fixed);
883 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
884 }
885 }
886
887 #[rstest]
888 #[case(1_000_000.0)]
889 #[case(-1_000_000.0)]
890 fn test_large_value_roundtrip(#[case] value: f64) {
891 for precision in 0..=FIXED_PRECISION {
892 let fixed = f64_to_fixed_i128(value, precision);
893 let result = fixed_i128_to_f64(fixed);
894 assert!(approx_eq!(f64, value, result, epsilon = 0.000_1));
895 }
896 }
897
898 #[rstest]
899 #[case(0, 123_456.0)]
900 #[case(0, 123_456.7)]
901 #[case(1, 123_456.7)]
902 #[case(2, 123_456.78)]
903 #[case(8, 123_456.123_456_78)]
904 fn test_precision_specific_values_basic(#[case] precision: u8, #[case] value: f64) {
905 let result = f64_to_fixed_i128(value, precision);
906 let back_converted = fixed_i128_to_f64(result);
907 let scale = 10.0_f64.powi(i32::from(precision));
909 let expected_rounded = (value * scale).round() / scale;
910 assert!((back_converted - expected_rounded).abs() < 1e-10);
911 }
912
913 #[rstest]
914 fn test_max_precision_values() {
915 let test_value = 123_456.123_456_789;
917 let result = f64_to_fixed_i128(test_value, FIXED_PRECISION);
918 let back_converted = fixed_i128_to_f64(result);
919 assert!((back_converted - test_value).abs() < 1e-6);
921 }
922
923 #[rstest]
924 #[case(0.0)]
925 #[case(1.0)]
926 #[case(1_000_000.0)]
927 fn test_unsigned_basic_roundtrip(#[case] value: f64) {
928 for precision in 0..=FIXED_PRECISION {
929 let fixed = f64_to_fixed_u128(value, precision);
930 let result = fixed_u128_to_f64(fixed);
931 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
932 }
933 }
934
935 #[rstest]
936 #[case(0)]
937 #[case(FIXED_PRECISION)]
938 fn test_valid_precision(#[case] precision: u8) {
939 let result = check_fixed_precision(precision);
940 assert!(result.is_ok());
941 }
942
943 #[cfg(not(feature = "defi"))]
944 #[rstest]
945 fn test_invalid_precision() {
946 let precision = FIXED_PRECISION + 1;
947 let result = check_fixed_precision(precision);
948 assert!(result.is_err());
949 }
950
951 #[cfg(feature = "defi")]
952 #[rstest]
953 fn test_invalid_precision() {
954 use crate::defi::WEI_PRECISION;
955 let precision = WEI_PRECISION + 1;
956 let result = check_fixed_precision(precision);
957 assert!(result.is_err());
958 }
959
960 #[cfg(not(feature = "defi"))]
961 #[rstest]
962 fn test_check_fixed_precision_returns_typed_error_with_stable_display() {
963 let error = check_fixed_precision(FIXED_PRECISION + 1).unwrap_err();
964
965 assert_eq!(
966 error,
967 CorrectnessError::PredicateViolation {
968 message: format!(
969 "`precision` exceeded maximum `FIXED_PRECISION` ({FIXED_PRECISION}), was {}",
970 FIXED_PRECISION + 1
971 ),
972 }
973 );
974 assert_eq!(
975 error.to_string(),
976 format!(
977 "`precision` exceeded maximum `FIXED_PRECISION` ({FIXED_PRECISION}), was {}",
978 FIXED_PRECISION + 1
979 )
980 );
981 }
982
983 #[cfg(feature = "defi")]
984 #[rstest]
985 fn test_check_fixed_precision_returns_typed_error_with_stable_display() {
986 use crate::defi::WEI_PRECISION;
987
988 let error = check_fixed_precision(WEI_PRECISION + 1).unwrap_err();
989
990 assert_eq!(
991 error,
992 CorrectnessError::PredicateViolation {
993 message: format!(
994 "`precision` exceeded maximum `WEI_PRECISION` (18), was {}",
995 WEI_PRECISION + 1
996 ),
997 }
998 );
999 assert_eq!(
1000 error.to_string(),
1001 format!(
1002 "`precision` exceeded maximum `WEI_PRECISION` (18), was {}",
1003 WEI_PRECISION + 1
1004 )
1005 );
1006 }
1007
1008 #[rstest]
1009 #[case(0, 0.0)]
1010 #[case(1, 1.0)]
1011 #[case(1, 1.1)]
1012 #[case(9, 0.000_000_001)]
1013 #[case(16, 0.000_000_000_000_000_1)]
1014 #[case(0, -0.0)]
1015 #[case(1, -1.0)]
1016 #[case(1, -1.1)]
1017 #[case(9, -0.000_000_001)]
1018 #[case(16, -0.000_000_000_000_000_1)]
1019 fn test_f64_to_fixed_i128_to_fixed(#[case] precision: u8, #[case] value: f64) {
1020 let fixed = f64_to_fixed_i128(value, precision);
1021 let result = fixed_i128_to_f64(fixed);
1022 assert_eq!(result, value);
1023 }
1024
1025 #[rstest]
1026 #[case(0, 0.0)]
1027 #[case(1, 1.0)]
1028 #[case(1, 1.1)]
1029 #[case(9, 0.000_000_001)]
1030 #[case(16, 0.000_000_000_000_000_1)]
1031 fn test_f64_to_fixed_u128_to_fixed(#[case] precision: u8, #[case] value: f64) {
1032 let fixed = f64_to_fixed_u128(value, precision);
1033 let result = fixed_u128_to_f64(fixed);
1034 assert_eq!(result, value);
1035 }
1036
1037 #[rstest]
1038 #[case(0, 123_456.0)]
1039 #[case(0, 123_456.7)]
1040 #[case(0, 123_456.4)]
1041 #[case(1, 123_456.0)]
1042 #[case(1, 123_456.7)]
1043 #[case(1, 123_456.4)]
1044 #[case(2, 123_456.0)]
1045 #[case(2, 123_456.7)]
1046 #[case(2, 123_456.4)]
1047 fn test_f64_to_fixed_i128_with_precision(#[case] precision: u8, #[case] value: f64) {
1048 let result = f64_to_fixed_i128(value, precision);
1049
1050 let pow1 = 10_i128.pow(u32::from(precision));
1052 let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
1053 let rounded = (value * pow1 as f64).round() as i128;
1054 let expected = rounded * pow2;
1055
1056 assert_eq!(
1057 result, expected,
1058 "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
1059 );
1060 }
1061
1062 #[rstest]
1063 #[case(0, 5.555_555_555_555_555)]
1064 #[case(1, 5.555_555_555_555_555)]
1065 #[case(2, 5.555_555_555_555_555)]
1066 #[case(3, 5.555_555_555_555_555)]
1067 #[case(4, 5.555_555_555_555_555)]
1068 #[case(5, 5.555_555_555_555_555)]
1069 #[case(6, 5.555_555_555_555_555)]
1070 #[case(7, 5.555_555_555_555_555)]
1071 #[case(8, 5.555_555_555_555_555)]
1072 #[case(9, 5.555_555_555_555_555)]
1073 #[case(10, 5.555_555_555_555_555)]
1074 #[case(11, 5.555_555_555_555_555)]
1075 #[case(12, 5.555_555_555_555_555)]
1076 #[case(13, 5.555_555_555_555_555)]
1077 #[case(14, 5.555_555_555_555_555)]
1078 #[case(15, 5.555_555_555_555_555)]
1079 #[case(0, -5.555_555_555_555_555)]
1080 #[case(1, -5.555_555_555_555_555)]
1081 #[case(2, -5.555_555_555_555_555)]
1082 #[case(3, -5.555_555_555_555_555)]
1083 #[case(4, -5.555_555_555_555_555)]
1084 #[case(5, -5.555_555_555_555_555)]
1085 #[case(6, -5.555_555_555_555_555)]
1086 #[case(7, -5.555_555_555_555_555)]
1087 #[case(8, -5.555_555_555_555_555)]
1088 #[case(9, -5.555_555_555_555_555)]
1089 #[case(10, -5.555_555_555_555_555)]
1090 #[case(11, -5.555_555_555_555_555)]
1091 #[case(12, -5.555_555_555_555_555)]
1092 #[case(13, -5.555_555_555_555_555)]
1093 #[case(14, -5.555_555_555_555_555)]
1094 #[case(15, -5.555_555_555_555_555)]
1095 fn test_f64_to_fixed_i128(#[case] precision: u8, #[case] value: f64) {
1096 if precision > FIXED_PRECISION {
1098 return;
1099 }
1100
1101 let result = f64_to_fixed_i128(value, precision);
1102
1103 let pow1 = 10_i128.pow(u32::from(precision));
1105 let pow2 = 10_i128.pow(u32::from(FIXED_PRECISION - precision));
1106 let rounded = (value * pow1 as f64).round() as i128;
1107 let expected = rounded * pow2;
1108
1109 assert_eq!(
1110 result, expected,
1111 "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
1112 );
1113 }
1114
1115 #[rstest]
1116 #[case(0, 5.555_555_555_555_555)]
1117 #[case(1, 5.555_555_555_555_555)]
1118 #[case(2, 5.555_555_555_555_555)]
1119 #[case(3, 5.555_555_555_555_555)]
1120 #[case(4, 5.555_555_555_555_555)]
1121 #[case(5, 5.555_555_555_555_555)]
1122 #[case(6, 5.555_555_555_555_555)]
1123 #[case(7, 5.555_555_555_555_555)]
1124 #[case(8, 5.555_555_555_555_555)]
1125 #[case(9, 5.555_555_555_555_555)]
1126 #[case(10, 5.555_555_555_555_555)]
1127 #[case(11, 5.555_555_555_555_555)]
1128 #[case(12, 5.555_555_555_555_555)]
1129 #[case(13, 5.555_555_555_555_555)]
1130 #[case(14, 5.555_555_555_555_555)]
1131 #[case(15, 5.555_555_555_555_555)]
1132 #[case(16, 5.555_555_555_555_555)]
1133 fn test_f64_to_fixed_u64(#[case] precision: u8, #[case] value: f64) {
1134 if precision > FIXED_PRECISION {
1136 return;
1137 }
1138
1139 let result = f64_to_fixed_u128(value, precision);
1140
1141 let pow1 = 10_u128.pow(u32::from(precision));
1143 let pow2 = 10_u128.pow(u32::from(FIXED_PRECISION - precision));
1144 let rounded = (value * pow1 as f64).round() as u128;
1145 let expected = rounded * pow2;
1146
1147 assert_eq!(
1148 result, expected,
1149 "Failed for precision {precision}, value {value}: got {result}, expected {expected}"
1150 );
1151 }
1152
1153 #[rstest]
1154 fn test_fixed_i128_to_f64(
1155 #[values(1, -1, 2, -2, 10, -10, 100, -100, 1_000, -1_000, -10_000, -100_000)] value: i128,
1156 ) {
1157 assert_eq!(fixed_i128_to_f64(value), value as f64 / FIXED_SCALAR);
1158 }
1159
1160 #[rstest]
1161 fn test_fixed_u128_to_f64(
1162 #[values(
1163 0,
1164 1,
1165 2,
1166 3,
1167 10,
1168 100,
1169 1_000,
1170 10_000,
1171 100_000,
1172 1_000_000,
1173 10_000_000,
1174 100_000_000,
1175 1_000_000_000,
1176 10_000_000_000,
1177 100_000_000_000,
1178 1_000_000_000_000,
1179 10_000_000_000_000,
1180 100_000_000_000_000,
1181 1_000_000_000_000_000,
1182 10_000_000_000_000_000,
1183 100_000_000_000_000_000,
1184 1_000_000_000_000_000_000,
1185 10_000_000_000_000_000_000,
1186 100_000_000_000_000_000_000
1187 )]
1188 value: u128,
1189 ) {
1190 let result = fixed_u128_to_f64(value);
1191 assert_eq!(result, (value as f64) / FIXED_SCALAR);
1192 }
1193
1194 #[rstest]
1199 #[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) {
1205 assert!(check_fixed_raw_u128(raw, precision).is_ok());
1206 }
1207
1208 #[rstest]
1209 #[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) {
1215 assert!(check_fixed_raw_u128(raw, precision).is_err());
1216 }
1217
1218 #[rstest]
1219 fn test_check_fixed_raw_u128_at_max_precision() {
1220 assert!(check_fixed_raw_u128(0, FIXED_PRECISION).is_ok());
1222 assert!(check_fixed_raw_u128(1, FIXED_PRECISION).is_ok());
1223 assert!(check_fixed_raw_u128(123_456_789, FIXED_PRECISION).is_ok());
1224 assert!(check_fixed_raw_u128(u128::MAX, FIXED_PRECISION).is_ok());
1225 }
1226
1227 #[rstest]
1228 #[case(0, 0)]
1229 #[case(0, 10_000_000_000_000_000)]
1230 #[case(0, -10_000_000_000_000_000)]
1231 #[case(8, 12_345_678_900_000_000)]
1232 #[case(8, -12_345_678_900_000_000)]
1233 fn test_check_fixed_raw_i128_valid(#[case] precision: u8, #[case] raw: i128) {
1234 assert!(check_fixed_raw_i128(raw, precision).is_ok());
1235 }
1236
1237 #[rstest]
1238 #[case(0, 1)]
1239 #[case(0, -1)]
1240 #[case(0, 9_999_999_999_999_999)]
1241 #[case(0, -9_999_999_999_999_999)]
1242 fn test_check_fixed_raw_i128_invalid(#[case] precision: u8, #[case] raw: i128) {
1243 assert!(check_fixed_raw_i128(raw, precision).is_err());
1244 }
1245
1246 #[rstest]
1247 fn test_check_fixed_raw_i128_at_max_precision() {
1248 assert!(check_fixed_raw_i128(0, FIXED_PRECISION).is_ok());
1249 assert!(check_fixed_raw_i128(1, FIXED_PRECISION).is_ok());
1250 assert!(check_fixed_raw_i128(-1, FIXED_PRECISION).is_ok());
1251 assert!(check_fixed_raw_i128(i128::MAX, FIXED_PRECISION).is_ok());
1252 assert!(check_fixed_raw_i128(i128::MIN, FIXED_PRECISION).is_ok());
1253 }
1254
1255 #[rstest]
1256 #[should_panic(expected = "Overflow when scaling f64 to fixed-point i128")]
1257 fn test_f64_to_fixed_i128_overflow_panics() {
1258 let _ = f64_to_fixed_i128(1e30, 0);
1259 }
1260
1261 #[rstest]
1262 #[should_panic(expected = "Overflow when scaling f64 to fixed-point u128")]
1263 fn test_f64_to_fixed_u128_overflow_panics() {
1264 let _ = f64_to_fixed_u128(1e30, 0);
1265 }
1266}
1267
1268#[cfg(not(feature = "high-precision"))]
1269#[cfg(test)]
1270mod tests {
1271 use nautilus_core::approx_eq;
1272 use rstest::rstest;
1273
1274 use super::*;
1275
1276 #[rstest]
1277 fn test_precision_boundaries() {
1278 assert!(check_fixed_precision(0).is_ok());
1279 assert!(check_fixed_precision(FIXED_PRECISION).is_ok());
1280 assert!(check_fixed_precision(FIXED_PRECISION + 1).is_err());
1281 }
1282
1283 #[rstest]
1284 #[case(0.0)]
1285 #[case(1.0)]
1286 #[case(-1.0)]
1287 fn test_basic_roundtrip(#[case] value: f64) {
1288 for precision in 0..=FIXED_PRECISION {
1289 let fixed = f64_to_fixed_i64(value, precision);
1290 let result = fixed_i64_to_f64(fixed);
1291 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
1292 }
1293 }
1294
1295 #[rstest]
1296 #[case(1000000.0)]
1297 #[case(-1000000.0)]
1298 fn test_large_value_roundtrip(#[case] value: f64) {
1299 for precision in 0..=FIXED_PRECISION {
1300 let fixed = f64_to_fixed_i64(value, precision);
1301 let result = fixed_i64_to_f64(fixed);
1302 assert!(approx_eq!(f64, value, result, epsilon = 0.000_1));
1303 }
1304 }
1305
1306 #[rstest]
1307 #[case(0, 123456.0, 123_456_000_000_000)]
1308 #[case(0, 123456.7, 123_457_000_000_000)]
1309 #[case(1, 123456.7, 123_456_700_000_000)]
1310 #[case(2, 123456.78, 123_456_780_000_000)]
1311 #[case(8, 123456.123_456_78, 123_456_123_456_780)]
1312 #[case(9, 123456.123_456_789, 123_456_123_456_789)]
1313 fn test_precision_specific_values(
1314 #[case] precision: u8,
1315 #[case] value: f64,
1316 #[case] expected: i64,
1317 ) {
1318 assert_eq!(f64_to_fixed_i64(value, precision), expected);
1319 }
1320
1321 #[rstest]
1322 #[case(0.0)]
1323 #[case(1.0)]
1324 #[case(1000000.0)]
1325 fn test_unsigned_basic_roundtrip(#[case] value: f64) {
1326 for precision in 0..=FIXED_PRECISION {
1327 let fixed = f64_to_fixed_u64(value, precision);
1328 let result = fixed_u64_to_f64(fixed);
1329 assert!(approx_eq!(f64, value, result, epsilon = 0.001));
1330 }
1331 }
1332
1333 #[rstest]
1334 #[case(0, 1.4, 1.0)]
1335 #[case(0, 1.5, 2.0)]
1336 #[case(0, 1.6, 2.0)]
1337 #[case(1, 1.44, 1.4)]
1338 #[case(1, 1.45, 1.5)]
1339 #[case(1, 1.46, 1.5)]
1340 #[case(2, 1.444, 1.44)]
1341 #[case(2, 1.445, 1.45)]
1342 #[case(2, 1.446, 1.45)]
1343 fn test_rounding(#[case] precision: u8, #[case] input: f64, #[case] expected: f64) {
1344 let fixed = f64_to_fixed_i128(input, precision);
1345 assert!(approx_eq!(
1346 f64,
1347 fixed_i128_to_f64(fixed),
1348 expected,
1349 epsilon = 0.000_000_001
1350 ));
1351 }
1352
1353 #[rstest]
1354 fn test_special_values() {
1355 assert_eq!(f64_to_fixed_i128(0.0, FIXED_PRECISION), 0);
1357 assert_eq!(f64_to_fixed_i128(-0.0, FIXED_PRECISION), 0);
1358
1359 let smallest_positive = 1.0 / FIXED_SCALAR;
1361 let fixed_smallest = f64_to_fixed_i128(smallest_positive, FIXED_PRECISION);
1362 assert_eq!(fixed_smallest, 1);
1363
1364 let large_int = 1_000_000_000.0;
1366 let fixed_large = f64_to_fixed_i128(large_int, 0);
1367 assert_eq!(fixed_i128_to_f64(fixed_large), large_int);
1368 }
1369
1370 #[rstest]
1371 #[case(0)]
1372 #[case(FIXED_PRECISION)]
1373 fn test_valid_precision(#[case] precision: u8) {
1374 let result = check_fixed_precision(precision);
1375 assert!(result.is_ok());
1376 }
1377
1378 #[rstest]
1379 fn test_invalid_precision() {
1380 let precision = FIXED_PRECISION + 1;
1381 let result = check_fixed_precision(precision);
1382 assert!(result.is_err());
1383 }
1384
1385 #[rstest]
1386 #[case(0, 0.0)]
1387 #[case(1, 1.0)]
1388 #[case(1, 1.1)]
1389 #[case(9, 0.000_000_001)]
1390 #[case(0, -0.0)]
1391 #[case(1, -1.0)]
1392 #[case(1, -1.1)]
1393 #[case(9, -0.000_000_001)]
1394 fn test_f64_to_fixed_i64_to_fixed(#[case] precision: u8, #[case] value: f64) {
1395 let fixed = f64_to_fixed_i64(value, precision);
1396 let result = fixed_i64_to_f64(fixed);
1397 assert_eq!(result, value);
1398 }
1399
1400 #[rstest]
1401 #[case(0, 0.0)]
1402 #[case(1, 1.0)]
1403 #[case(1, 1.1)]
1404 #[case(9, 0.000_000_001)]
1405 fn test_f64_to_fixed_u64_to_fixed(#[case] precision: u8, #[case] value: f64) {
1406 let fixed = f64_to_fixed_u64(value, precision);
1407 let result = fixed_u64_to_f64(fixed);
1408 assert_eq!(result, value);
1409 }
1410
1411 #[rstest]
1412 #[case(0, 123456.0, 123_456_000_000_000)]
1413 #[case(0, 123456.7, 123_457_000_000_000)]
1414 #[case(0, 123_456.4, 123_456_000_000_000)]
1415 #[case(1, 123456.0, 123_456_000_000_000)]
1416 #[case(1, 123456.7, 123_456_700_000_000)]
1417 #[case(1, 123_456.4, 123_456_400_000_000)]
1418 #[case(2, 123456.0, 123_456_000_000_000)]
1419 #[case(2, 123456.7, 123_456_700_000_000)]
1420 #[case(2, 123_456.4, 123_456_400_000_000)]
1421 fn test_f64_to_fixed_i64_with_precision(
1422 #[case] precision: u8,
1423 #[case] value: f64,
1424 #[case] expected: i64,
1425 ) {
1426 assert_eq!(f64_to_fixed_i64(value, precision), expected);
1427 }
1428
1429 #[rstest]
1430 #[case(0, 5.5, 6_000_000_000)]
1431 #[case(1, 5.55, 5_600_000_000)]
1432 #[case(2, 5.555, 5_560_000_000)]
1433 #[case(3, 5.5555, 5_556_000_000)]
1434 #[case(4, 5.55555, 5_555_600_000)]
1435 #[case(5, 5.555_555, 5_555_560_000)]
1436 #[case(6, 5.555_555_5, 5_555_556_000)]
1437 #[case(7, 5.555_555_55, 5_555_555_600)]
1438 #[case(8, 5.555_555_555, 5_555_555_560)]
1439 #[case(9, 5.555_555_555_5, 5_555_555_556)]
1440 #[case(0, -5.5, -6_000_000_000)]
1441 #[case(1, -5.55, -5_600_000_000)]
1442 #[case(2, -5.555, -5_560_000_000)]
1443 #[case(3, -5.5555, -5_556_000_000)]
1444 #[case(4, -5.55555, -5_555_600_000)]
1445 #[case(5, -5.555_555, -5_555_560_000)]
1446 #[case(6, -5.555_555_5, -5_555_556_000)]
1447 #[case(7, -5.555_555_55, -5_555_555_600)]
1448 #[case(8, -5.555_555_555, -5_555_555_560)]
1449 #[case(9, -5.555_555_555_5, -5_555_555_556)]
1450 fn test_f64_to_fixed_i64(#[case] precision: u8, #[case] value: f64, #[case] expected: i64) {
1451 assert_eq!(f64_to_fixed_i64(value, precision), expected);
1452 }
1453
1454 #[rstest]
1455 #[case(0, 5.5, 6_000_000_000)]
1456 #[case(1, 5.55, 5_600_000_000)]
1457 #[case(2, 5.555, 5_560_000_000)]
1458 #[case(3, 5.5555, 5_556_000_000)]
1459 #[case(4, 5.55555, 5_555_600_000)]
1460 #[case(5, 5.555_555, 5_555_560_000)]
1461 #[case(6, 5.555_555_5, 5_555_556_000)]
1462 #[case(7, 5.555_555_55, 5_555_555_600)]
1463 #[case(8, 5.555_555_555, 5_555_555_560)]
1464 #[case(9, 5.555_555_555_5, 5_555_555_556)]
1465 fn test_f64_to_fixed_u64(#[case] precision: u8, #[case] value: f64, #[case] expected: u64) {
1466 assert_eq!(f64_to_fixed_u64(value, precision), expected);
1467 }
1468
1469 #[rstest]
1470 fn test_fixed_i64_to_f64(
1471 #[values(1, -1, 2, -2, 10, -10, 100, -100, 1_000, -1_000)] value: i64,
1472 ) {
1473 assert_eq!(fixed_i64_to_f64(value), value as f64 / FIXED_SCALAR);
1474 }
1475
1476 #[rstest]
1477 fn test_fixed_u64_to_f64(
1478 #[values(
1479 0,
1480 1,
1481 2,
1482 3,
1483 10,
1484 100,
1485 1_000,
1486 10_000,
1487 100_000,
1488 1_000_000,
1489 10_000_000,
1490 100_000_000,
1491 1_000_000_000,
1492 10_000_000_000,
1493 100_000_000_000,
1494 1_000_000_000_000,
1495 10_000_000_000_000,
1496 100_000_000_000_000,
1497 1_000_000_000_000_000
1498 )]
1499 value: u64,
1500 ) {
1501 let result = fixed_u64_to_f64(value);
1502 assert_eq!(result, (value as f64) / FIXED_SCALAR);
1503 }
1504
1505 #[rstest]
1506 #[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) {
1512 assert!(check_fixed_raw_u64(raw, precision).is_ok());
1513 }
1514
1515 #[rstest]
1516 #[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) {
1523 assert!(check_fixed_raw_u64(raw, precision).is_err());
1524 }
1525
1526 #[rstest]
1527 fn test_check_fixed_raw_u64_at_max_precision() {
1528 assert!(check_fixed_raw_u64(0, FIXED_PRECISION).is_ok());
1530 assert!(check_fixed_raw_u64(1, FIXED_PRECISION).is_ok());
1531 assert!(check_fixed_raw_u64(123_456_789, FIXED_PRECISION).is_ok());
1532 assert!(check_fixed_raw_u64(u64::MAX, FIXED_PRECISION).is_ok());
1533 }
1534
1535 #[rstest]
1536 #[case(0, 0)]
1537 #[case(0, 1_000_000_000)]
1538 #[case(0, -1_000_000_000)]
1539 #[case(2, 123_450_000_000)]
1540 #[case(2, -123_450_000_000)]
1541 fn test_check_fixed_raw_i64_valid(#[case] precision: u8, #[case] raw: i64) {
1542 assert!(check_fixed_raw_i64(raw, precision).is_ok());
1543 }
1544
1545 #[rstest]
1546 #[case(0, 1)]
1547 #[case(0, -1)]
1548 #[case(0, 999_999_999)]
1549 #[case(0, -999_999_999)]
1550 fn test_check_fixed_raw_i64_invalid(#[case] precision: u8, #[case] raw: i64) {
1551 assert!(check_fixed_raw_i64(raw, precision).is_err());
1552 }
1553
1554 #[rstest]
1555 fn test_check_fixed_raw_i64_at_max_precision() {
1556 assert!(check_fixed_raw_i64(0, FIXED_PRECISION).is_ok());
1557 assert!(check_fixed_raw_i64(1, FIXED_PRECISION).is_ok());
1558 assert!(check_fixed_raw_i64(-1, FIXED_PRECISION).is_ok());
1559 assert!(check_fixed_raw_i64(i64::MAX, FIXED_PRECISION).is_ok());
1560 assert!(check_fixed_raw_i64(i64::MIN, FIXED_PRECISION).is_ok());
1561 }
1562
1563 #[rstest]
1564 #[should_panic(expected = "Overflow when scaling f64 to fixed-point i64")]
1565 fn test_f64_to_fixed_i64_overflow_panics() {
1566 let _ = f64_to_fixed_i64(2e18, 0);
1567 }
1568
1569 #[rstest]
1570 #[should_panic(expected = "Overflow when scaling f64 to fixed-point u64")]
1571 fn test_f64_to_fixed_u64_overflow_panics() {
1572 let _ = f64_to_fixed_u64(2e19, 0);
1573 }
1574}
1575
1576#[cfg(test)]
1577mod bankers_round_tests {
1578 use std::str::FromStr;
1579
1580 use rstest::rstest;
1581 use rust_decimal::{Decimal, RoundingStrategy};
1582
1583 use super::*;
1584
1585 #[rstest]
1586 #[case(0, 0, 0)]
1588 #[case(1, 0, 1)]
1589 #[case(5, 0, 5)]
1590 #[case(99, 0, 99)]
1591 #[case(-7, 0, -7)]
1592 #[case(12345, 39, 0)]
1594 #[case(i128::from(i64::MAX), 100, 0)]
1595 #[case(-99999, 50, 0)]
1596 #[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)]
1653 #[case(0, 2, 0)]
1654 #[case(0, 5, 0)]
1655 #[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)]
1663 #[case(1_000_000_000_000_000_005, 1, 100_000_000_000_000_000)]
1664 fn test_bankers_round(#[case] mantissa: i128, #[case] excess: u32, #[case] expected: i128) {
1665 assert_eq!(
1666 bankers_round(mantissa, excess),
1667 expected,
1668 "bankers_round({mantissa}, {excess}) expected {expected}"
1669 );
1670 }
1671
1672 #[rstest]
1674 #[case(15, 1)]
1675 #[case(25, 1)]
1676 #[case(35, 1)]
1677 #[case(150, 2)]
1678 #[case(250, 2)]
1679 #[case(1500, 3)]
1680 #[case(2500, 3)]
1681 #[case(123_456_789, 3)]
1682 #[case(14, 1)]
1683 #[case(16, 1)]
1684 fn test_bankers_round_negative_symmetry(#[case] mantissa: i128, #[case] excess: u32) {
1685 assert_eq!(
1686 bankers_round(-mantissa, excess),
1687 -bankers_round(mantissa, excess),
1688 "Negative symmetry failed for mantissa={mantissa}, excess={excess}"
1689 );
1690 }
1691
1692 #[rstest]
1694 #[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")]
1702 #[case("-3.5", 0, "-4")]
1703 #[case("123.456", 2, "123.46")]
1704 #[case("123.455", 2, "123.46")] #[case("123.445", 2, "123.44")] fn test_bankers_round_matches_decimal(
1707 #[case] input: &str,
1708 #[case] target_precision: u8,
1709 #[case] expected: &str,
1710 ) {
1711 let dec = Decimal::from_str(input).unwrap();
1712 let expected_dec = Decimal::from_str(expected).unwrap();
1713
1714 let decimal_rounded = dec.round_dp_with_strategy(
1715 u32::from(target_precision),
1716 RoundingStrategy::MidpointNearestEven,
1717 );
1718 assert_eq!(
1719 decimal_rounded, expected_dec,
1720 "Decimal rounding sanity check failed for {input}"
1721 );
1722
1723 let mantissa = dec.mantissa();
1724 let scale = dec.scale() as u8;
1725 let excess = u32::from(scale.saturating_sub(target_precision));
1726 if excess > 0 {
1727 let rounded = bankers_round(mantissa, excess);
1728
1729 let expected_mantissa = expected_dec.mantissa();
1731 let expected_scale = expected_dec.scale() as u8;
1732 let scale_diff = u32::from(target_precision.saturating_sub(expected_scale));
1733 let normalized_expected = expected_mantissa * 10i128.pow(scale_diff);
1734
1735 assert_eq!(
1736 rounded, normalized_expected,
1737 "bankers_round disagrees with Decimal for {input} at precision {target_precision}"
1738 );
1739 }
1740 }
1741}
1742
1743#[cfg(test)]
1744mod correct_raw_tests {
1745 use rstest::rstest;
1746
1747 use super::*;
1748
1749 #[rstest]
1753 #[case(0, 0)]
1754 #[case(10, 10)] #[case(14, 10)] #[case(15, 20)] #[case(16, 20)] #[case(u64::MAX, u64::MAX - 5)] fn test_correct_raw_u64(#[case] raw: u64, #[case] expected: u64) {
1760 assert_eq!(correct_raw_u64(raw, FIXED_PRECISION - 1), expected);
1761 }
1762
1763 #[rstest]
1764 #[case(0, 0)]
1765 #[case(14, 10)]
1766 #[case(15, 20)]
1767 #[case(-14, -10)] #[case(-15, -20)] #[case(-16, -20)] #[case(i64::MAX, i64::MAX - 7)] #[case(i64::MIN, i64::MIN + 8)] fn test_correct_raw_i64(#[case] raw: i64, #[case] expected: i64) {
1773 assert_eq!(correct_raw_i64(raw, FIXED_PRECISION - 1), expected);
1774 }
1775
1776 #[rstest]
1777 #[case(0, 0)]
1778 #[case(14, 10)]
1779 #[case(15, 20)]
1780 #[case(u128::MAX, u128::MAX - 5)] fn test_correct_raw_u128(#[case] raw: u128, #[case] expected: u128) {
1782 assert_eq!(correct_raw_u128(raw, FIXED_PRECISION - 1), expected);
1783 }
1784
1785 #[rstest]
1786 #[case(0, 0)]
1787 #[case(14, 10)]
1788 #[case(15, 20)]
1789 #[case(-15, -20)]
1790 #[case(i128::MAX, i128::MAX - 7)] #[case(i128::MIN, i128::MIN + 8)] fn test_correct_raw_i128(#[case] raw: i128, #[case] expected: i128) {
1793 assert_eq!(correct_raw_i128(raw, FIXED_PRECISION - 1), expected);
1794 }
1795
1796 #[rstest]
1797 fn test_correct_raw_identity_at_max_precision() {
1798 assert_eq!(correct_raw_u64(12_345, FIXED_PRECISION), 12_345);
1799 assert_eq!(correct_raw_i64(-12_345, FIXED_PRECISION), -12_345);
1800 assert_eq!(correct_raw_u128(12_345, FIXED_PRECISION), 12_345);
1801 assert_eq!(correct_raw_i128(-12_345, FIXED_PRECISION), -12_345);
1802 }
1803}