fixed_num_validator/
lib.rs1pub use bigdecimal::BigDecimal;
2use std::str::FromStr;
3use std::fmt::{Debug, Display};
4use fixed_num_helper::*;
5
6#[derive(Clone, Debug)]
7pub struct Series {
8 pub seed: u64,
9 pub int_prec: RandRange,
10 pub frac_prec: RandRange,
11}
12
13impl Series {
14 pub fn new(int_prec: impl IntoRandRange, frac_prec: impl IntoRandRange) -> Self {
15 let int_prec = int_prec.into_rand_range();
16 let frac_prec = frac_prec.into_rand_range();
17 Self {
18 seed: 0,
19 int_prec,
20 frac_prec,
21 }
22 }
23}
24
25pub fn series_str<T>(cfg: Series) -> Vec<String>
26where T: Rand + Display {
27 let count = 10_000;
28 let seed_base = cfg.seed * 1_000_000;
29 (0..count)
30 .map(|i| T::rand(seed_base + i, cfg.int_prec.clone(), cfg.frac_prec.clone()))
31 .map(|t| format!("{t}"))
32 .collect()
33}
34
35pub fn series_pair1<A, B>(mut cfg: Series) -> Vec<(A, B)> where
36A: Rand + Display + FromStr<Err:Debug>,
37B: FromStr<Err:Debug> {
38 if cfg.seed == 0 { cfg.seed = 7; }
39 let vec_str = series_str::<A>(cfg);
40 let a_vec = vec_str.iter().map(|a| A::from_str(a).unwrap()).collect::<Vec<_>>();
41 let b_vec = vec_str.iter().map(|a| B::from_str(a).unwrap()).collect::<Vec<_>>();
42 a_vec.into_iter().zip(b_vec.into_iter()).collect::<Vec<_>>()
43}
44
45pub fn series_pair2<A, B>(mut cfg1: Series, mut cfg2: Series) -> Vec<((A, B), (A, B))> where
46A: Rand + Display + FromStr<Err:Debug>,
47B: FromStr<Err:Debug> {
48 if cfg1.seed == 0 { cfg1.seed = 7; }
49 if cfg2.seed == 0 { cfg2.seed = 17; }
50 series_pair1(cfg1).into_iter().zip(series_pair1(cfg2).into_iter()).collect()
51}
52
53pub fn fuzzy1<A, B>(cfg1: Series, f: impl Fn(A, B)) where
54 A: Rand + Display + FromStr<Err:Debug>,
55 B: FromStr<Err:Debug> {
56 for (a, b) in series_pair1::<A, B>(cfg1) {
57 f(a, b);
58 }
59}
60
61pub fn fuzzy2<A, B>(cfg1: Series, cfg2: Series, f: impl Fn((A, B), (A, B))) where
62A: Rand + Display + FromStr<Err:Debug>,
63B: FromStr<Err:Debug> {
64 for (a, b) in series_pair2::<A, B>(cfg1, cfg2) {
65 f(a, b);
66 }
67}
68
69pub fn should_panic<T: Debug>(f: impl FnOnce() -> T + std::panic::UnwindSafe, desc: &str) {
70 let result = std::panic::catch_unwind(|| f());
71 assert!(result.is_err(), "Expected panic, but got: {result:?} in {desc}");
72}
73
74
75pub fn cmp<T>(a: T, b: BigDecimal) -> Result<(), String>
76where T: Copy + Display {
77 let a_str = format!("{a:.19}");
78 let b_str = format!("{:.19}", b.with_scale(19));
79 if a_str == b_str {
80 Ok(())
81 } else {
82 Err(format!("Mismatch: {a_str} != {b_str}"))
83 }
84}
85
86pub trait ShouldEq<T> {
87 fn should_eq(self, other: T);
88}
89
90impl<T> ShouldEq<BigDecimal> for T
91where T: Display {
92 fn should_eq(self, other: BigDecimal) {
93 let a_str = format!("{self:.19}");
94 let b_str = format!("{:.19}", other.with_scale(19));
95 assert_eq!(a_str, b_str, "Mismatch: {a_str} != {b_str}");
96 }
97}
98
99pub fn should_eq<A, B>(a: A, b: B)
100where A: ShouldEq<B> {
101 a.should_eq(b);
102}
103
104#[macro_export]
105macro_rules! check {
106 ( [] $cases:tt ) => {};
107 ( [$f:expr $(, $($fns:tt)*)?] $cases:tt ) => {
108 check! { @1 $f, $cases }
109 check! { [$($($fns)*)?] $cases }
110 };
111 ( @1 $f:expr, {}) => {
112
113 };
114 ( @1 $f:expr, { $args:tt => FAIL $(, $($ts:tt)* )? } ) => {
115 check! { @2 $f, $args => FAIL }
116 check! { @1 $f, { $($($ts)*)? } }
117 };
118 ( @1 $f:expr, { $args:tt => $out:expr $(, $($ts:tt)* )? } ) => {
119 check! { @2 $f, $args => $out }
120 check! { @1 $f, { $($($ts)*)? } }
121 };
122 ( @2 $f:expr, ($($args:tt)*) => FAIL ) => {
123 should_panic(|| $f($($args)*).unwrap_all(), stringify!($f($($args)*) != FAIL));
124 };
125 ( @2 $f:expr, ($($args:tt)*) => $out:expr ) => {
126 assert_eq!($f($($args)*).unwrap_all(), $out, stringify!($f($($args)*) != $out));
127 };
128}