blob: e5656eb204c733ab8933f0ddd21be3463e0b75d8 [file] [log] [blame]
ThiƩbaud Weksteend4f2c3c2020-11-03 11:08:00 +01001#![cfg(not(target_arch = "wasm32"))]
2
3use std::str;
4
5use core::num::flt2dec::strategy::grisu::format_exact_opt;
6use core::num::flt2dec::strategy::grisu::format_shortest_opt;
7use core::num::flt2dec::MAX_SIG_DIGITS;
8use core::num::flt2dec::{decode, DecodableFloat, Decoded, FullDecoded};
9
10use rand::distributions::{Distribution, Uniform};
11use rand::rngs::StdRng;
12use rand::SeedableRng;
13
14pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
15 match decode(v).1 {
16 FullDecoded::Finite(decoded) => decoded,
17 full_decoded => panic!("expected finite, got {:?} instead", full_decoded),
18 }
19}
20
21fn iterate<F, G, V>(func: &str, k: usize, n: usize, mut f: F, mut g: G, mut v: V) -> (usize, usize)
22where
23 F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
24 G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
25 V: FnMut(usize) -> Decoded,
26{
27 assert!(k <= 1024);
28
29 let mut npassed = 0; // f(x) = Some(g(x))
30 let mut nignored = 0; // f(x) = None
31
32 for i in 0..n {
33 if (i & 0xfffff) == 0 {
34 println!(
35 "in progress, {:x}/{:x} (ignored={} passed={} failed={})",
36 i,
37 n,
38 nignored,
39 npassed,
40 i - nignored - npassed
41 );
42 }
43
44 let decoded = v(i);
45 let mut buf1 = [0; 1024];
46 if let Some((len1, e1)) = f(&decoded, &mut buf1[..k]) {
47 let mut buf2 = [0; 1024];
48 let (len2, e2) = g(&decoded, &mut buf2[..k]);
49 if e1 == e2 && &buf1[..len1] == &buf2[..len2] {
50 npassed += 1;
51 } else {
52 println!(
53 "equivalence test failed, {:x}/{:x}: {:?} f(i)={}e{} g(i)={}e{}",
54 i,
55 n,
56 decoded,
57 str::from_utf8(&buf1[..len1]).unwrap(),
58 e1,
59 str::from_utf8(&buf2[..len2]).unwrap(),
60 e2
61 );
62 }
63 } else {
64 nignored += 1;
65 }
66 }
67 println!(
68 "{}({}): done, ignored={} passed={} failed={}",
69 func,
70 k,
71 nignored,
72 npassed,
73 n - nignored - npassed
74 );
75 assert!(
76 nignored + npassed == n,
77 "{}({}): {} out of {} values returns an incorrect value!",
78 func,
79 k,
80 n - nignored - npassed,
81 n
82 );
83 (npassed, nignored)
84}
85
86pub fn f32_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
87where
88 F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
89 G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
90{
91 if cfg!(target_os = "emscripten") {
92 return; // using rng pulls in i128 support, which doesn't work
93 }
94 let mut rng = StdRng::from_entropy();
95 let f32_range = Uniform::new(0x0000_0001u32, 0x7f80_0000);
96 iterate("f32_random_equivalence_test", k, n, f, g, |_| {
97 let x = f32::from_bits(f32_range.sample(&mut rng));
98 decode_finite(x)
99 });
100}
101
102pub fn f64_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
103where
104 F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
105 G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
106{
107 if cfg!(target_os = "emscripten") {
108 return; // using rng pulls in i128 support, which doesn't work
109 }
110 let mut rng = StdRng::from_entropy();
111 let f64_range = Uniform::new(0x0000_0000_0000_0001u64, 0x7ff0_0000_0000_0000);
112 iterate("f64_random_equivalence_test", k, n, f, g, |_| {
113 let x = f64::from_bits(f64_range.sample(&mut rng));
114 decode_finite(x)
115 });
116}
117
118pub fn f32_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
119where
120 F: FnMut(&Decoded, &mut [u8]) -> Option<(usize, i16)>,
121 G: FnMut(&Decoded, &mut [u8]) -> (usize, i16),
122{
123 // we have only 2^23 * (2^8 - 1) - 1 = 2,139,095,039 positive finite f32 values,
124 // so why not simply testing all of them?
125 //
126 // this is of course very stressful (and thus should be behind an `#[ignore]` attribute),
127 // but with `-C opt-level=3 -C lto` this only takes about an hour or so.
128
129 // iterate from 0x0000_0001 to 0x7f7f_ffff, i.e., all finite ranges
130 let (npassed, nignored) =
131 iterate("f32_exhaustive_equivalence_test", k, 0x7f7f_ffff, f, g, |i: usize| {
132 let x = f32::from_bits(i as u32 + 1);
133 decode_finite(x)
134 });
135 assert_eq!((npassed, nignored), (2121451881, 17643158));
136}
137
138#[test]
139fn shortest_random_equivalence_test() {
140 use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
141 // Miri is too slow
142 let n = if cfg!(miri) { 10 } else { 10_000 };
143
144 f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
145 f32_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
146}
147
148#[test]
149#[ignore] // it is too expensive
150fn shortest_f32_exhaustive_equivalence_test() {
151 // it is hard to directly test the optimality of the output, but we can at least test if
152 // two different algorithms agree to each other.
153 //
154 // this reports the progress and the number of f32 values returned `None`.
155 // with `--nocapture` (and plenty of time and appropriate rustc flags), this should print:
156 // `done, ignored=17643158 passed=2121451881 failed=0`.
157
158 use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
159 f32_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS);
160}
161
162#[test]
163#[ignore] // it is too expensive
164fn shortest_f64_hard_random_equivalence_test() {
165 // this again probably has to use appropriate rustc flags.
166
167 use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
168 f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, 100_000_000);
169}
170
171#[test]
172fn exact_f32_random_equivalence_test() {
173 use core::num::flt2dec::strategy::dragon::format_exact as fallback;
174 // Miri is too slow
175 let n = if cfg!(miri) { 3 } else { 1_000 };
176
177 for k in 1..21 {
178 f32_random_equivalence_test(
179 |d, buf| format_exact_opt(d, buf, i16::MIN),
180 |d, buf| fallback(d, buf, i16::MIN),
181 k,
182 n,
183 );
184 }
185}
186
187#[test]
188fn exact_f64_random_equivalence_test() {
189 use core::num::flt2dec::strategy::dragon::format_exact as fallback;
190 // Miri is too slow
191 let n = if cfg!(miri) { 2 } else { 1_000 };
192
193 for k in 1..21 {
194 f64_random_equivalence_test(
195 |d, buf| format_exact_opt(d, buf, i16::MIN),
196 |d, buf| fallback(d, buf, i16::MIN),
197 k,
198 n,
199 );
200 }
201}