ThiƩbaud Weksteen | d4f2c3c | 2020-11-03 11:08:00 +0100 | [diff] [blame] | 1 | #![cfg(not(target_arch = "wasm32"))] |
| 2 | |
| 3 | use std::str; |
| 4 | |
| 5 | use core::num::flt2dec::strategy::grisu::format_exact_opt; |
| 6 | use core::num::flt2dec::strategy::grisu::format_shortest_opt; |
| 7 | use core::num::flt2dec::MAX_SIG_DIGITS; |
| 8 | use core::num::flt2dec::{decode, DecodableFloat, Decoded, FullDecoded}; |
| 9 | |
| 10 | use rand::distributions::{Distribution, Uniform}; |
| 11 | use rand::rngs::StdRng; |
| 12 | use rand::SeedableRng; |
| 13 | |
| 14 | pub 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 | |
| 21 | fn iterate<F, G, V>(func: &str, k: usize, n: usize, mut f: F, mut g: G, mut v: V) -> (usize, usize) |
| 22 | where |
| 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 | |
| 86 | pub fn f32_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize) |
| 87 | where |
| 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 | |
| 102 | pub fn f64_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize) |
| 103 | where |
| 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 | |
| 118 | pub fn f32_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize) |
| 119 | where |
| 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] |
| 139 | fn 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 |
| 150 | fn 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 |
| 164 | fn 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] |
| 172 | fn 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] |
| 188 | fn 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 | } |