diff options
| author | onur-ozkan <work@onurozkan.dev> | 2025-06-03 10:38:15 +0300 |
|---|---|---|
| committer | onur-ozkan <work@onurozkan.dev> | 2025-06-03 11:05:51 +0300 |
| commit | 59fbe04a5249d2da56b19bf0e7f4f017064a3866 (patch) | |
| tree | 7c6477a240400851fc48d08e0d53eb21a51fb6be /src/etc | |
| parent | 99426c570eebec8dcba2eaa8f5057265346aaedc (diff) | |
| download | rust-59fbe04a5249d2da56b19bf0e7f4f017064a3866.tar.gz rust-59fbe04a5249d2da56b19bf0e7f4f017064a3866.zip | |
move `test-float-parse` tool into `src/tools` dir
Obviously `test-float-parse` is a tool like any other in `src/tools`. Signed-off-by: onur-ozkan <work@onurozkan.dev>
Diffstat (limited to 'src/etc')
| -rw-r--r-- | src/etc/test-float-parse/Cargo.lock | 75 | ||||
| -rw-r--r-- | src/etc/test-float-parse/Cargo.toml | 22 | ||||
| -rw-r--r-- | src/etc/test-float-parse/README.md | 55 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/exhaustive.rs | 42 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/exponents.rs | 95 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/fuzz.rs | 87 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/integers.rs | 104 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/long_fractions.rs | 58 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/many_digits.rs | 84 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/sparse.rs | 99 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/spot_checks.rs | 101 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/gen_/subnorm.rs | 108 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/lib.rs | 419 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/main.rs | 129 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/traits.rs | 206 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/ui.rs | 168 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/validate.rs | 394 | ||||
| -rw-r--r-- | src/etc/test-float-parse/src/validate/tests.rs | 149 |
18 files changed, 0 insertions, 2395 deletions
diff --git a/src/etc/test-float-parse/Cargo.lock b/src/etc/test-float-parse/Cargo.lock deleted file mode 100644 index 3f60423fed3..00000000000 --- a/src/etc/test-float-parse/Cargo.lock +++ /dev/null @@ -1,75 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "test-float-parse" -version = "0.1.0" -dependencies = [ - "rand", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/src/etc/test-float-parse/Cargo.toml b/src/etc/test-float-parse/Cargo.toml deleted file mode 100644 index e407e322f9e..00000000000 --- a/src/etc/test-float-parse/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "test-float-parse" -version = "0.1.0" -edition = "2024" -publish = false - -[dependencies] -indicatif = { version = "0.17.8", default-features = false } -num = "0.4.3" -rand = "0.9.0" -rand_chacha = "0.9.0" -rayon = "1" - -[lib] -name = "test_float_parse" - -[lints.rust.unexpected_cfgs] -level = "warn" -check-cfg = [ - # Internal features aren't marked known config by default - 'cfg(target_has_reliable_f16)', -] diff --git a/src/etc/test-float-parse/README.md b/src/etc/test-float-parse/README.md deleted file mode 100644 index 5e2c43d1cad..00000000000 --- a/src/etc/test-float-parse/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Float Parsing Tests - -These are tests designed to test decimal to float conversions (`dec2flt`) used -by the standard library. - -It consists of a collection of test generators that each generate a set of -patterns intended to test a specific property. In addition, there are exhaustive -tests (for <= `f32`) and fuzzers (for anything that can't be run exhaustively). - -The generators work as follows: - -- Each generator is a struct that lives somewhere in the `gen` module. Usually - it is generic over a float type. -- These generators must implement `Iterator`, which should return a context type - that can be used to construct a test string (but usually not the string - itself). -- They must also implement the `Generator` trait, which provides a method to - write test context to a string as a test case, as well as some extra metadata. - - The split between context generation and string construction is so that we can - reuse string allocations. -- Each generator gets registered once for each float type. Each of these - generators then get their iterator called, and each test case checked against - the float type's parse implementation. - -Some generators produce decimal strings, others create bit patterns that need to -be bitcasted to the float type, which then uses its `Display` implementation to -write to a string. For these, float to decimal (`flt2dec`) conversions also get -tested, if unintentionally. - -For each test case, the following is done: - -- The test string is parsed to the float type using the standard library's - implementation. -- The test string is parsed separately to a `BigRational`, which acts as a - representation with infinite precision. -- The rational value then gets checked that it is within the float's - representable values (absolute value greater than the smallest number to round - to zero, but less less than the first value to round to infinity). If these - limits are exceeded, check that the parsed float reflects that. -- For real nonzero numbers, the parsed float is converted into a rational using - `significand * 2^exponent`. It is then checked against the actual rational - value, and verified to be within half a bit's precision of the parsed value. - Also it is checked that ties round to even. - -This is all highly parallelized with `rayon`; test generators can run in -parallel, and their tests get chunked and run in parallel. - -There is a simple command line that allows filtering which tests are run, -setting the number of iterations for fuzzing tests, limiting failures, setting -timeouts, etc. See `main.rs` or run with `--help` for options. - -Note that when running via `./x`, only tests that take less than a few minutes -are run by default. Navigate to the crate (or pass `-C` to Cargo) and run it -directly to run all tests or pass specific arguments. diff --git a/src/etc/test-float-parse/src/gen_/exhaustive.rs b/src/etc/test-float-parse/src/gen_/exhaustive.rs deleted file mode 100644 index 01458fb0b60..00000000000 --- a/src/etc/test-float-parse/src/gen_/exhaustive.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::fmt::Write; -use std::ops::RangeInclusive; - -use crate::{Float, Generator, Int}; - -/// Test every possible bit pattern. This is infeasible to run on any float types larger than -/// `f32` (which takes about an hour). -pub struct Exhaustive<F: Float> { - iter: RangeInclusive<F::Int>, -} - -impl<F: Float> Generator<F> for Exhaustive<F> -where - RangeInclusive<F::Int>: Iterator<Item = F::Int>, -{ - const SHORT_NAME: &'static str = "exhaustive"; - - type WriteCtx = F; - - fn total_tests() -> u64 { - 1u64.checked_shl(F::Int::BITS).expect("More than u64::MAX tests") - } - - fn new() -> Self { - Self { iter: F::Int::ZERO..=F::Int::MAX } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx:e}").unwrap(); - } -} - -impl<F: Float> Iterator for Exhaustive<F> -where - RangeInclusive<F::Int>: Iterator<Item = F::Int>, -{ - type Item = F; - - fn next(&mut self) -> Option<Self::Item> { - Some(F::from_bits(self.iter.next()?)) - } -} diff --git a/src/etc/test-float-parse/src/gen_/exponents.rs b/src/etc/test-float-parse/src/gen_/exponents.rs deleted file mode 100644 index 3748e9d380c..00000000000 --- a/src/etc/test-float-parse/src/gen_/exponents.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::fmt::Write; -use std::ops::RangeInclusive; - -use crate::traits::BoxGenIter; -use crate::{Float, Generator}; - -const SMALL_COEFF_MAX: i32 = 10_000; -const SMALL_EXP_MAX: i32 = 300; - -const SMALL_COEFF_RANGE: RangeInclusive<i32> = (-SMALL_COEFF_MAX)..=SMALL_COEFF_MAX; -const SMALL_EXP_RANGE: RangeInclusive<i32> = (-SMALL_EXP_MAX)..=SMALL_EXP_MAX; - -const LARGE_COEFF_RANGE: RangeInclusive<u32> = 0..=100_000; -const LARGE_EXP_RANGE: RangeInclusive<u32> = 300..=350; - -/// Check exponential values around zero. -pub struct SmallExponents<F: Float> { - iter: BoxGenIter<Self, F>, -} - -impl<F: Float> Generator<F> for SmallExponents<F> { - const NAME: &'static str = "small exponents"; - const SHORT_NAME: &'static str = "small exp"; - - /// `(coefficient, exponent)` - type WriteCtx = (i32, i32); - - fn total_tests() -> u64 { - ((1 + SMALL_COEFF_RANGE.end() - SMALL_COEFF_RANGE.start()) - * (1 + SMALL_EXP_RANGE.end() - SMALL_EXP_RANGE.start())) - .try_into() - .unwrap() - } - - fn new() -> Self { - let iter = SMALL_EXP_RANGE.flat_map(|exp| SMALL_COEFF_RANGE.map(move |coeff| (coeff, exp))); - - Self { iter: Box::new(iter) } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - let (coeff, exp) = ctx; - write!(s, "{coeff}e{exp}").unwrap(); - } -} - -impl<F: Float> Iterator for SmallExponents<F> { - type Item = (i32, i32); - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next() - } -} - -/// Check exponential values further from zero. -pub struct LargeExponents<F: Float> { - iter: BoxGenIter<Self, F>, -} - -impl<F: Float> Generator<F> for LargeExponents<F> { - const NAME: &'static str = "large positive exponents"; - const SHORT_NAME: &'static str = "large exp"; - - /// `(coefficient, exponent, is_positive)` - type WriteCtx = (u32, u32, bool); - - fn total_tests() -> u64 { - ((1 + LARGE_EXP_RANGE.end() - LARGE_EXP_RANGE.start()) - * (1 + LARGE_COEFF_RANGE.end() - LARGE_COEFF_RANGE.start()) - * 2) - .into() - } - - fn new() -> Self { - let iter = LARGE_EXP_RANGE - .flat_map(|exp| LARGE_COEFF_RANGE.map(move |coeff| (coeff, exp))) - .flat_map(|(coeff, exp)| [(coeff, exp, false), (coeff, exp, true)]); - - Self { iter: Box::new(iter) } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - let (coeff, exp, is_positive) = ctx; - let sign = if is_positive { "" } else { "-" }; - write!(s, "{sign}{coeff}e{exp}").unwrap(); - } -} - -impl<F: Float> Iterator for LargeExponents<F> { - type Item = (u32, u32, bool); - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next() - } -} diff --git a/src/etc/test-float-parse/src/gen_/fuzz.rs b/src/etc/test-float-parse/src/gen_/fuzz.rs deleted file mode 100644 index 1d6c5562a14..00000000000 --- a/src/etc/test-float-parse/src/gen_/fuzz.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::any::{TypeId, type_name}; -use std::collections::BTreeMap; -use std::fmt::Write; -use std::marker::PhantomData; -use std::ops::Range; -use std::sync::Mutex; - -use rand::Rng; -use rand::distr::{Distribution, StandardUniform}; -use rand_chacha::ChaCha8Rng; -use rand_chacha::rand_core::SeedableRng; - -use crate::{Float, Generator, Int, SEED}; - -/// Mapping of float types to the number of iterations that should be run. -/// -/// We could probably make `Generator::new` take an argument instead of the global state, -/// but we only load this once so it works. -static FUZZ_COUNTS: Mutex<BTreeMap<TypeId, u64>> = Mutex::new(BTreeMap::new()); - -/// Generic fuzzer; just tests deterministic random bit patterns N times. -pub struct Fuzz<F> { - iter: Range<u64>, - rng: ChaCha8Rng, - /// Allow us to use generics in `Iterator`. - marker: PhantomData<F>, -} - -impl<F: Float> Fuzz<F> { - /// Register how many iterations the fuzzer should run for a type. Uses some logic by - /// default, but if `from_cfg` is `Some`, that will be used instead. - pub fn set_iterations(from_cfg: Option<u64>) { - let count = if let Some(cfg_count) = from_cfg { - cfg_count - } else if F::BITS <= crate::MAX_BITS_FOR_EXHAUUSTIVE { - // If we run exhaustively, still fuzz but only do half as many bits. The only goal here is - // to catch failures from e.g. high bit patterns before exhaustive tests would get to them. - (F::Int::MAX >> (F::BITS / 2)).try_into().unwrap() - } else { - // Eveything bigger gets a fuzz test with as many iterations as `f32` exhaustive. - u32::MAX.into() - }; - - let _ = FUZZ_COUNTS.lock().unwrap().insert(TypeId::of::<F>(), count); - } -} - -impl<F: Float> Generator<F> for Fuzz<F> -where - StandardUniform: Distribution<<F as Float>::Int>, -{ - const SHORT_NAME: &'static str = "fuzz"; - - type WriteCtx = F; - - fn total_tests() -> u64 { - *FUZZ_COUNTS - .lock() - .unwrap() - .get(&TypeId::of::<F>()) - .unwrap_or_else(|| panic!("missing fuzz count for {}", type_name::<F>())) - } - - fn new() -> Self { - let rng = ChaCha8Rng::from_seed(SEED); - - Self { iter: 0..Self::total_tests(), rng, marker: PhantomData } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx:e}").unwrap(); - } -} - -impl<F: Float> Iterator for Fuzz<F> -where - StandardUniform: Distribution<<F as Float>::Int>, -{ - type Item = <Self as Generator<F>>::WriteCtx; - - fn next(&mut self) -> Option<Self::Item> { - let _ = self.iter.next()?; - let i: F::Int = self.rng.random(); - - Some(F::from_bits(i)) - } -} diff --git a/src/etc/test-float-parse/src/gen_/integers.rs b/src/etc/test-float-parse/src/gen_/integers.rs deleted file mode 100644 index 070d188e88c..00000000000 --- a/src/etc/test-float-parse/src/gen_/integers.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::fmt::Write; -use std::ops::{Range, RangeInclusive}; - -use crate::traits::BoxGenIter; -use crate::{Float, Generator}; - -const SMALL_MAX_POW2: u32 = 19; - -/// All values up to the max power of two -const SMALL_VALUES: RangeInclusive<i32> = { - let max = 1i32 << SMALL_MAX_POW2; - (-max)..=max -}; - -/// Large values only get tested around powers of two -const LARGE_POWERS: Range<u32> = SMALL_MAX_POW2..128; - -/// We perturbe each large value around these ranges -const LARGE_PERTURBATIONS: RangeInclusive<i128> = -256..=256; - -/// Test all integers up to `2 ^ MAX_POW2` -pub struct SmallInt { - iter: RangeInclusive<i32>, -} - -impl<F: Float> Generator<F> for SmallInt { - const NAME: &'static str = "small integer values"; - const SHORT_NAME: &'static str = "int small"; - - type WriteCtx = i32; - - fn total_tests() -> u64 { - (SMALL_VALUES.end() + 1 - SMALL_VALUES.start()).try_into().unwrap() - } - - fn new() -> Self { - Self { iter: SMALL_VALUES } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx}").unwrap(); - } -} - -impl Iterator for SmallInt { - type Item = i32; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next() - } -} - -/// Test much bigger integers than [`SmallInt`]. -pub struct LargeInt<F: Float> { - iter: BoxGenIter<Self, F>, -} - -impl<F: Float> LargeInt<F> { - const EDGE_CASES: [i128; 7] = [ - i32::MIN as i128, - i32::MAX as i128, - i64::MIN as i128, - i64::MAX as i128, - u64::MAX as i128, - i128::MIN, - i128::MAX, - ]; -} - -impl<F: Float> Generator<F> for LargeInt<F> { - const NAME: &'static str = "large integer values"; - const SHORT_NAME: &'static str = "int large"; - - type WriteCtx = i128; - - fn total_tests() -> u64 { - u64::try_from( - (i128::from(LARGE_POWERS.end - LARGE_POWERS.start) - + i128::try_from(Self::EDGE_CASES.len()).unwrap()) - * (LARGE_PERTURBATIONS.end() + 1 - LARGE_PERTURBATIONS.start()), - ) - .unwrap() - } - - fn new() -> Self { - let iter = LARGE_POWERS - .map(|pow| 1i128 << pow) - .chain(Self::EDGE_CASES) - .flat_map(|base| LARGE_PERTURBATIONS.map(move |perturb| base.saturating_add(perturb))); - - Self { iter: Box::new(iter) } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx}").unwrap(); - } -} -impl<F: Float> Iterator for LargeInt<F> { - type Item = i128; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next() - } -} diff --git a/src/etc/test-float-parse/src/gen_/long_fractions.rs b/src/etc/test-float-parse/src/gen_/long_fractions.rs deleted file mode 100644 index b75148b779c..00000000000 --- a/src/etc/test-float-parse/src/gen_/long_fractions.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::char; -use std::fmt::Write; - -use crate::{Float, Generator}; - -/// Number of decimal digits to check (all of them). -const MAX_DIGIT: u32 = 9; -/// Test with this many decimals in the string. -const MAX_DECIMALS: usize = 410; -const PREFIX: &str = "0."; - -/// Test e.g. `0.1`, `0.11`, `0.111`, `0.1111`, ..., `0.2`, `0.22`, ... -pub struct RepeatingDecimal { - digit: u32, - buf: String, -} - -impl<F: Float> Generator<F> for RepeatingDecimal { - const NAME: &'static str = "repeating decimal"; - const SHORT_NAME: &'static str = "dec rep"; - - type WriteCtx = String; - - fn total_tests() -> u64 { - u64::from(MAX_DIGIT + 1) * u64::try_from(MAX_DECIMALS + 1).unwrap() + 1 - } - - fn new() -> Self { - Self { digit: 0, buf: PREFIX.to_owned() } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - *s = ctx; - } -} - -impl Iterator for RepeatingDecimal { - type Item = String; - - fn next(&mut self) -> Option<Self::Item> { - if self.digit > MAX_DIGIT { - return None; - } - - let digit = self.digit; - let inc_digit = self.buf.len() - PREFIX.len() > MAX_DECIMALS; - - if inc_digit { - // Reset the string - self.buf.clear(); - self.digit += 1; - self.buf.write_str(PREFIX).unwrap(); - } - - self.buf.push(char::from_digit(digit, 10).unwrap()); - Some(self.buf.clone()) - } -} diff --git a/src/etc/test-float-parse/src/gen_/many_digits.rs b/src/etc/test-float-parse/src/gen_/many_digits.rs deleted file mode 100644 index 741e11437fe..00000000000 --- a/src/etc/test-float-parse/src/gen_/many_digits.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::char; -use std::fmt::Write; -use std::marker::PhantomData; -use std::ops::{Range, RangeInclusive}; - -use rand::distr::{Distribution, Uniform}; -use rand::{Rng, SeedableRng}; -use rand_chacha::ChaCha8Rng; - -use crate::{Float, Generator, SEED}; - -/// Total iterations -const ITERATIONS: u64 = 5_000_000; - -/// Possible lengths of the string, excluding decimals and exponents -const POSSIBLE_NUM_DIGITS: RangeInclusive<usize> = 100..=400; - -/// Range of possible exponents -const EXP_RANGE: Range<i32> = -4500..4500; - -/// Try strings of random digits. -pub struct RandDigits<F> { - rng: ChaCha8Rng, - iter: Range<u64>, - uniform: Uniform<u32>, - /// Allow us to use generics in `Iterator`. - marker: PhantomData<F>, -} - -impl<F: Float> Generator<F> for RandDigits<F> { - const NAME: &'static str = "random digits"; - - const SHORT_NAME: &'static str = "rand digits"; - - type WriteCtx = String; - - fn total_tests() -> u64 { - ITERATIONS - } - - fn new() -> Self { - let rng = ChaCha8Rng::from_seed(SEED); - let range = Uniform::try_from(0..10).unwrap(); - - Self { rng, iter: 0..ITERATIONS, uniform: range, marker: PhantomData } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - *s = ctx; - } -} - -impl<F: Float> Iterator for RandDigits<F> { - type Item = String; - - fn next(&mut self) -> Option<Self::Item> { - let _ = self.iter.next()?; - let num_digits = self.rng.random_range(POSSIBLE_NUM_DIGITS); - let has_decimal = self.rng.random_bool(0.2); - let has_exp = self.rng.random_bool(0.2); - - let dec_pos = if has_decimal { Some(self.rng.random_range(0..num_digits)) } else { None }; - - let mut s = String::with_capacity(num_digits); - - for pos in 0..num_digits { - let digit = char::from_digit(self.uniform.sample(&mut self.rng), 10).unwrap(); - s.push(digit); - - if let Some(dec_pos) = dec_pos { - if pos == dec_pos { - s.push('.'); - } - } - } - - if has_exp { - let exp = self.rng.random_range(EXP_RANGE); - write!(s, "e{exp}").unwrap(); - } - - Some(s) - } -} diff --git a/src/etc/test-float-parse/src/gen_/sparse.rs b/src/etc/test-float-parse/src/gen_/sparse.rs deleted file mode 100644 index 72b65d4ce7f..00000000000 --- a/src/etc/test-float-parse/src/gen_/sparse.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::fmt::Write; - -use crate::traits::BoxGenIter; -use crate::{Float, Generator}; - -const POWERS_OF_TWO: [u128; 128] = make_powers_of_two(); - -const fn make_powers_of_two() -> [u128; 128] { - let mut ret = [0; 128]; - let mut i = 0; - while i < 128 { - ret[i] = 1 << i; - i += 1; - } - - ret -} - -/// Can't clone this result because of lifetime errors, just use a macro. -macro_rules! pow_iter { - () => { - (0..F::BITS).map(|i| F::Int::try_from(POWERS_OF_TWO[i as usize]).unwrap()) - }; -} - -/// Test all numbers that include three 1s in the binary representation as integers. -pub struct FewOnesInt<F: Float> -where - FewOnesInt<F>: Generator<F>, -{ - iter: BoxGenIter<Self, F>, -} - -impl<F: Float> Generator<F> for FewOnesInt<F> -where - <F::Int as TryFrom<u128>>::Error: std::fmt::Debug, -{ - const SHORT_NAME: &'static str = "few ones int"; - - type WriteCtx = F::Int; - - fn total_tests() -> u64 { - u64::from(F::BITS).pow(3) - } - - fn new() -> Self { - let iter = pow_iter!() - .flat_map(move |a| pow_iter!().map(move |b| (a, b))) - .flat_map(move |(a, b)| pow_iter!().map(move |c| (a, b, c))) - .map(|(a, b, c)| a | b | c); - - Self { iter: Box::new(iter) } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx}").unwrap(); - } -} - -impl<F: Float> Iterator for FewOnesInt<F> { - type Item = F::Int; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next() - } -} - -/// Similar to `FewOnesInt` except test those bit patterns as a float. -pub struct FewOnesFloat<F: Float>(FewOnesInt<F>); - -impl<F: Float> Generator<F> for FewOnesFloat<F> -where - <F::Int as TryFrom<u128>>::Error: std::fmt::Debug, -{ - const NAME: &'static str = "few ones float"; - const SHORT_NAME: &'static str = "few ones float"; - - type WriteCtx = F; - - fn total_tests() -> u64 { - FewOnesInt::<F>::total_tests() - } - - fn new() -> Self { - Self(FewOnesInt::new()) - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx:e}").unwrap(); - } -} - -impl<F: Float> Iterator for FewOnesFloat<F> { - type Item = F; - - fn next(&mut self) -> Option<Self::Item> { - self.0.next().map(|i| F::from_bits(i)) - } -} diff --git a/src/etc/test-float-parse/src/gen_/spot_checks.rs b/src/etc/test-float-parse/src/gen_/spot_checks.rs deleted file mode 100644 index 18691f9d6cf..00000000000 --- a/src/etc/test-float-parse/src/gen_/spot_checks.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::fmt::Write; - -use crate::traits::{Float, Generator}; - -const SPECIAL: &[&str] = &[ - "inf", "Inf", "iNf", "INF", "-inf", "-Inf", "-iNf", "-INF", "+inf", "+Inf", "+iNf", "+INF", - "nan", "NaN", "NAN", "nAn", "-nan", "-NaN", "-NAN", "-nAn", "+nan", "+NaN", "+NAN", "+nAn", - "1", "-1", "+1", "1e1", "-1e1", "+1e1", "1e-1", "-1e-1", "+1e-1", "1e+1", "-1e+1", "+1e+1", - "1E1", "-1E1", "+1E1", "1E-1", "-1E-1", "+1E-1", "1E+1", "-1E+1", "+1E+1", "0", "-0", "+0", -]; - -/// Check various non-numeric special strings. -pub struct Special { - iter: std::slice::Iter<'static, &'static str>, -} - -impl<F: Float> Generator<F> for Special { - const NAME: &'static str = "special values"; - - const SHORT_NAME: &'static str = "special"; - - type WriteCtx = &'static str; - - fn total_tests() -> u64 { - SPECIAL.len().try_into().unwrap() - } - - fn new() -> Self { - Self { iter: SPECIAL.iter() } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - s.write_str(ctx).unwrap(); - } -} - -impl Iterator for Special { - type Item = &'static str; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next().copied() - } -} - -/// Strings that we know have failed in the past -const REGRESSIONS: &[&str] = &[ - // From <https://github.com/rust-lang/rust/issues/31407> - "1234567890123456789012345678901234567890e-340", - "2.225073858507201136057409796709131975934819546351645648023426109724822222021076945516529523908135087914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012981122451451889849057222307285255133155755015914397476397983411801999323962548289017107081850690630666655994938275772572015763062690663332647565300009245888316433037779791869612049497390377829704905051080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621572289880258182545180325707018860872113128079512233426288368622321503775666622503982534335974568884423900265498198385487948292206894721689831099698365846814022854243330660339850886445804001034933970427567186443383770486037861622771738545623065874679014086723327636718749999999999999999999999999999999999999e-308", - "2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508791414915891303962110687008643869459464552765720740782062174337998814106326732925355228688137214901298112245145188984905722230728525513315575501591439747639798341180199932396254828901710708185069063066665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505108060994073026293712895895000358379996720725430436028407889577179615094551674824347103070260914462157228988025818254518032570701886087211312807951223342628836862232150377566662250398253433597456888442390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042756718644338377048603786162277173854562306587467901408672332763671875e-308", - "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000222507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581100515243416155346010885601238537771882113077799353200233047961014744258363607192156504694250373420837525080665061665815894872049117996859163964850063590877011830487479978088775374994945158045160505091539985658247081864511353793580499211598108576605199243335211435239014879569960959128889160299264151106346631339366347758651302937176204732563178148566435087212282863764204484681140761391147706280168985324411002416144742161856716615054015428508471675290190316132277889672970737312333408698898317506783884692609277397797285865965494109136909540613646756870239867831529068098461721092462539672851562500000000000000001", - "179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497791.9999999999999999999999999999999999999999999999999999999999999999999999", - "2.47032822920623272e-324", - "6.631236871469758276785396630275967243399099947355303144249971758736286630139265439618068200788048744105960420552601852889715006376325666595539603330361800519107591783233358492337208057849499360899425128640718856616503093444922854759159988160304439909868291973931426625698663157749836252274523485312442358651207051292453083278116143932569727918709786004497872322193856150225415211997283078496319412124640111777216148110752815101775295719811974338451936095907419622417538473679495148632480391435931767981122396703443803335529756003353209830071832230689201383015598792184172909927924176339315507402234836120730914783168400715462440053817592702766213559042115986763819482654128770595766806872783349146967171293949598850675682115696218943412532098591327667236328125E-316", - "3.237883913302901289588352412501532174863037669423108059901297049552301970670676565786835742587799557860615776559838283435514391084153169252689190564396459577394618038928365305143463955100356696665629202017331344031730044369360205258345803431471660032699580731300954848363975548690010751530018881758184174569652173110473696022749934638425380623369774736560008997404060967498028389191878963968575439222206416981462690113342524002724385941651051293552601421155333430225237291523843322331326138431477823591142408800030775170625915670728657003151953664260769822494937951845801530895238439819708403389937873241463484205608000027270531106827387907791444918534771598750162812548862768493201518991668028251730299953143924168545708663913273994694463908672332763671875E-319", - "6.953355807847677105972805215521891690222119817145950754416205607980030131549636688806115726399441880065386399864028691275539539414652831584795668560082999889551357784961446896042113198284213107935110217162654939802416034676213829409720583759540476786936413816541621287843248433202369209916612249676005573022703244799714622116542188837770376022371172079559125853382801396219552418839469770514904192657627060319372847562301074140442660237844114174497210955449896389180395827191602886654488182452409583981389442783377001505462015745017848754574668342161759496661766020028752888783387074850773192997102997936619876226688096314989645766000479009083731736585750335262099860150896718774401964796827166283225641992040747894382698751809812609536720628966577351093292236328125E-310", - "3.339068557571188581835713701280943911923401916998521771655656997328440314559615318168849149074662609099998113009465566426808170378434065722991659642619467706034884424989741080790766778456332168200464651593995817371782125010668346652995912233993254584461125868481633343674905074271064409763090708017856584019776878812425312008812326260363035474811532236853359905334625575404216060622858633280744301892470300555678734689978476870369853549413277156622170245846166991655321535529623870646888786637528995592800436177901746286272273374471701452991433047257863864601424252024791567368195056077320885329384322332391564645264143400798619665040608077549162173963649264049738362290606875883456826586710961041737908872035803481241600376705491726170293986797332763671875E-319", - "2.4703282292062327208828439643411068618252990130716238221279284125033775363510437593264991818081799618989828234772285886546332835517796989819938739800539093906315035659515570226392290858392449105184435931802849936536152500319370457678249219365623669863658480757001585769269903706311928279558551332927834338409351978015531246597263579574622766465272827220056374006485499977096599470454020828166226237857393450736339007967761930577506740176324673600968951340535537458516661134223766678604162159680461914467291840300530057530849048765391711386591646239524912623653881879636239373280423891018672348497668235089863388587925628302755995657524455507255189313690836254779186948667994968324049705821028513185451396213837722826145437693412532098591327667236328124999e-324", - "2.4703282292062327208828439643411068618252990130716238221279284125033775363510437593264991818081799618989828234772285886546332835517796989819938739800539093906315035659515570226392290858392449105184435931802849936536152500319370457678249219365623669863658480757001585769269903706311928279558551332927834338409351978015531246597263579574622766465272827220056374006485499977096599470454020828166226237857393450736339007967761930577506740176324673600968951340535537458516661134223766678604162159680461914467291840300530057530849048765391711386591646239524912623653881879636239373280423891018672348497668235089863388587925628302755995657524455507255189313690836254779186948667994968324049705821028513185451396213837722826145437693412532098591327667236328125e-324", - "2.4703282292062327208828439643411068618252990130716238221279284125033775363510437593264991818081799618989828234772285886546332835517796989819938739800539093906315035659515570226392290858392449105184435931802849936536152500319370457678249219365623669863658480757001585769269903706311928279558551332927834338409351978015531246597263579574622766465272827220056374006485499977096599470454020828166226237857393450736339007967761930577506740176324673600968951340535537458516661134223766678604162159680461914467291840300530057530849048765391711386591646239524912623653881879636239373280423891018672348497668235089863388587925628302755995657524455507255189313690836254779186948667994968324049705821028513185451396213837722826145437693412532098591327667236328125001e-324", - "7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984374999e-324", - "7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984375e-324", - "7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984375001e-324", - "94393431193180696942841837085033647913224148539854e-358", - "104308485241983990666713401708072175773165034278685682646111762292409330928739751702404658197872319129036519947435319418387839758990478549477777586673075945844895981012024387992135617064532141489278815239849108105951619997829153633535314849999674266169258928940692239684771590065027025835804863585454872499320500023126142553932654370362024104462255244034053203998964360882487378334860197725139151265590832887433736189468858614521708567646743455601905935595381852723723645799866672558576993978025033590728687206296379801363024094048327273913079612469982585674824156000783167963081616214710691759864332339239688734656548790656486646106983450809073750535624894296242072010195710276073042036425579852459556183541199012652571123898996574563824424330960027873516082763671875e-1075", - "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865463328355177969898199387398005390939063150356595155702263922908583924491051844359318028499365361525003193704576782492193656236698636584807570015857692699037063119282795585513329278343384093519780155312465972635795746227664652728272200563740064854999770965994704540208281662262378573934507363390079677619305775067401763246736009689513405355374585166611342237666786041621596804619144672918403005300575308490487653917113865916462395249126236538818796362393732804238910186723484976682350898633885879256283027559956575244555072551893136908362547791869486679949683240497058210285131854513962138377228261454376934125320985913276672363281249", - "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865463328355177969898199387398005390939063150356595155702263922908583924491051844359318028499365361525003193704576782492193656236698636584807570015857692699037063119282795585513329278343384093519780155312465972635795746227664652728272200563740064854999770965994704540208281662262378573934507363390079677619305775067401763246736009689513405355374585166611342237666786041621596804619144672918403005300575308490487653917113865916462395249126236538818796362393732804238910186723484976682350898633885879256283027559956575244555072551893136908362547791869486679949683240497058210285131854513962138377228261454376934125320985913276672363281251", -]; - -/// Check items that failed in the past. -pub struct RegressionCheck { - iter: std::slice::Iter<'static, &'static str>, -} - -impl<F: Float> Generator<F> for RegressionCheck { - const NAME: &'static str = "regression check"; - - const SHORT_NAME: &'static str = "regression"; - - type WriteCtx = &'static str; - - fn total_tests() -> u64 { - REGRESSIONS.len().try_into().unwrap() - } - - fn new() -> Self { - Self { iter: REGRESSIONS.iter() } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - s.write_str(ctx).unwrap(); - } -} - -impl Iterator for RegressionCheck { - type Item = &'static str; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next().copied() - } -} diff --git a/src/etc/test-float-parse/src/gen_/subnorm.rs b/src/etc/test-float-parse/src/gen_/subnorm.rs deleted file mode 100644 index 654f324b9b0..00000000000 --- a/src/etc/test-float-parse/src/gen_/subnorm.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::fmt::Write; -use std::ops::RangeInclusive; - -use crate::{Float, Generator, Int}; - -/// Spot check some edge cases for subnormals. -pub struct SubnormEdgeCases<F: Float> { - cases: [F::Int; 6], - index: usize, -} - -impl<F: Float> SubnormEdgeCases<F> { - /// Shorthand - const I1: F::Int = F::Int::ONE; - - fn edge_cases() -> [F::Int; 6] { - // Comments use an 8-bit mantissa as a demo - [ - // 0b00000001 - Self::I1, - // 0b10000000 - Self::I1 << (F::MAN_BITS - 1), - // 0b00001000 - Self::I1 << ((F::MAN_BITS / 2) - 1), - // 0b00001111 - Self::I1 << ((F::MAN_BITS / 2) - 1), - // 0b00001111 - Self::I1 << ((F::MAN_BITS / 2) - 1), - // 0b11111111 - F::MAN_MASK, - ] - } -} - -impl<F: Float> Generator<F> for SubnormEdgeCases<F> { - const NAME: &'static str = "subnormal edge cases"; - const SHORT_NAME: &'static str = "subnorm edge"; - - type WriteCtx = F; - - fn new() -> Self { - Self { cases: Self::edge_cases(), index: 0 } - } - - fn total_tests() -> u64 { - Self::edge_cases().len().try_into().unwrap() - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx:e}").unwrap(); - } -} - -impl<F: Float> Iterator for SubnormEdgeCases<F> { - type Item = F; - - fn next(&mut self) -> Option<Self::Item> { - let i = self.cases.get(self.index)?; - self.index += 1; - - Some(F::from_bits(*i)) - } -} - -/// Test all subnormals up to `1 << 22`. -pub struct SubnormComplete<F: Float> { - iter: RangeInclusive<F::Int>, -} - -impl<F: Float> Generator<F> for SubnormComplete<F> -where - RangeInclusive<F::Int>: Iterator<Item = F::Int>, -{ - const NAME: &'static str = "subnormal"; - const SHORT_NAME: &'static str = "subnorm "; - - type WriteCtx = F; - - fn total_tests() -> u64 { - let iter = Self::new().iter; - (F::Int::ONE + *iter.end() - *iter.start()).try_into().unwrap() - } - - fn new() -> Self { - let upper_lim = if F::MAN_BITS >= 22 { - F::Int::ONE << 22 - } else { - (F::Int::ONE << F::MAN_BITS) - F::Int::ONE - }; - - Self { iter: F::Int::ZERO..=upper_lim } - } - - fn write_string(s: &mut String, ctx: Self::WriteCtx) { - write!(s, "{ctx:e}").unwrap(); - } -} - -impl<F: Float> Iterator for SubnormComplete<F> -where - RangeInclusive<F::Int>: Iterator<Item = F::Int>, -{ - type Item = F; - - fn next(&mut self) -> Option<Self::Item> { - Some(F::from_bits(self.iter.next()?)) - } -} diff --git a/src/etc/test-float-parse/src/lib.rs b/src/etc/test-float-parse/src/lib.rs deleted file mode 100644 index f590149523b..00000000000 --- a/src/etc/test-float-parse/src/lib.rs +++ /dev/null @@ -1,419 +0,0 @@ -#![feature(f16)] -#![feature(cfg_target_has_reliable_f16_f128)] -#![expect(internal_features)] // reliable_f16_f128 - -mod traits; -mod ui; -mod validate; - -use std::any::type_name; -use std::cmp::min; -use std::ops::RangeInclusive; -use std::process::ExitCode; -use std::sync::OnceLock; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::{fmt, time}; - -use rand::distr::{Distribution, StandardUniform}; -use rayon::prelude::*; -use time::{Duration, Instant}; -use traits::{Float, Generator, Int}; -use validate::CheckError; - -/// Test generators. -mod gen_ { - pub mod exhaustive; - pub mod exponents; - pub mod fuzz; - pub mod integers; - pub mod long_fractions; - pub mod many_digits; - pub mod sparse; - pub mod spot_checks; - pub mod subnorm; -} - -/// How many failures to exit after if unspecified. -const DEFAULT_MAX_FAILURES: u64 = 20; - -/// Register exhaustive tests only for <= 32 bits. No more because it would take years. -const MAX_BITS_FOR_EXHAUUSTIVE: u32 = 32; - -/// If there are more tests than this threshold, the test will be deferred until after all -/// others run (so as to avoid thread pool starvation). They also can be excluded with -/// `--skip-huge`. -const HUGE_TEST_CUTOFF: u64 = 5_000_000; - -/// Seed for tests that use a deterministic RNG. -const SEED: [u8; 32] = *b"3.141592653589793238462643383279"; - -/// Global configuration. -#[derive(Debug)] -pub struct Config { - pub timeout: Duration, - /// Failures per test - pub max_failures: u64, - pub disable_max_failures: bool, - /// If `None`, the default will be used - pub fuzz_count: Option<u64>, - pub skip_huge: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - timeout: Duration::from_secs(60 * 60 * 3), - max_failures: DEFAULT_MAX_FAILURES, - disable_max_failures: false, - fuzz_count: None, - skip_huge: false, - } - } -} - -/// Collect, filter, and launch all tests. -pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode { - // With default parallelism, the CPU doesn't saturate. We don't need to be nice to - // other processes, so do 1.5x to make sure we use all available resources. - let threads = std::thread::available_parallelism().map(Into::into).unwrap_or(0) * 3 / 2; - rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap(); - - let mut tests = register_tests(&cfg); - println!("registered"); - let initial_tests: Vec<_> = tests.iter().map(|t| t.name.clone()).collect(); - - let unmatched: Vec<_> = include - .iter() - .chain(exclude.iter()) - .filter(|filt| !tests.iter().any(|t| t.matches(filt))) - .collect(); - - assert!( - unmatched.is_empty(), - "filters were provided that have no matching tests: {unmatched:#?}" - ); - - tests.retain(|test| !exclude.iter().any(|exc| test.matches(exc))); - - if cfg.skip_huge { - tests.retain(|test| !test.is_huge_test()); - } - - if !include.is_empty() { - tests.retain(|test| include.iter().any(|inc| test.matches(inc))); - } - - for exc in initial_tests.iter().filter(|orig_name| !tests.iter().any(|t| t.name == **orig_name)) - { - println!("Skipping test '{exc}'"); - } - - println!("Launching all"); - let elapsed = launch_tests(&mut tests, &cfg); - ui::finish_all(&tests, elapsed, &cfg) -} - -/// Enumerate tests to run but don't actually run them. -pub fn register_tests(cfg: &Config) -> Vec<TestInfo> { - let mut tests = Vec::new(); - - // Register normal generators for all floats. - - #[cfg(not(bootstrap))] - #[cfg(target_has_reliable_f16)] - register_float::<f16>(&mut tests, cfg); - register_float::<f32>(&mut tests, cfg); - register_float::<f64>(&mut tests, cfg); - - tests.sort_unstable_by_key(|t| (t.float_name, t.gen_name)); - for i in 0..(tests.len() - 1) { - if tests[i].gen_name == tests[i + 1].gen_name { - panic!("duplicate test name {}", tests[i].gen_name); - } - } - - tests -} - -/// Register all generators for a single float. -fn register_float<F: Float>(tests: &mut Vec<TestInfo>, cfg: &Config) -where - RangeInclusive<F::Int>: Iterator<Item = F::Int>, - <F::Int as TryFrom<u128>>::Error: std::fmt::Debug, - StandardUniform: Distribution<<F as traits::Float>::Int>, -{ - if F::BITS <= MAX_BITS_FOR_EXHAUUSTIVE { - // Only run exhaustive tests if there is a chance of completion. - TestInfo::register::<F, gen_::exhaustive::Exhaustive<F>>(tests); - } - - gen_::fuzz::Fuzz::<F>::set_iterations(cfg.fuzz_count); - - TestInfo::register::<F, gen_::exponents::LargeExponents<F>>(tests); - TestInfo::register::<F, gen_::exponents::SmallExponents<F>>(tests); - TestInfo::register::<F, gen_::fuzz::Fuzz<F>>(tests); - TestInfo::register::<F, gen_::integers::LargeInt<F>>(tests); - TestInfo::register::<F, gen_::integers::SmallInt>(tests); - TestInfo::register::<F, gen_::long_fractions::RepeatingDecimal>(tests); - TestInfo::register::<F, gen_::many_digits::RandDigits<F>>(tests); - TestInfo::register::<F, gen_::sparse::FewOnesFloat<F>>(tests); - TestInfo::register::<F, gen_::sparse::FewOnesInt<F>>(tests); - TestInfo::register::<F, gen_::spot_checks::RegressionCheck>(tests); - TestInfo::register::<F, gen_::spot_checks::Special>(tests); - TestInfo::register::<F, gen_::subnorm::SubnormComplete<F>>(tests); - TestInfo::register::<F, gen_::subnorm::SubnormEdgeCases<F>>(tests); -} - -/// Configuration for a single test. -#[derive(Debug)] -pub struct TestInfo { - pub name: String, - float_name: &'static str, - float_bits: u32, - gen_name: &'static str, - /// Name for display in the progress bar. - short_name: String, - /// Pad the short name to a common width for progress bar use. - short_name_padded: String, - total_tests: u64, - /// Function to launch this test. - launch: fn(&TestInfo, &Config), - /// Progress bar to be updated. - progress: Option<ui::Progress>, - /// Once completed, this will be set. - completed: OnceLock<Completed>, -} - -impl TestInfo { - /// Check if either the name or short name is a match, for filtering. - fn matches(&self, pat: &str) -> bool { - self.short_name.contains(pat) || self.name.contains(pat) - } - - /// Create a `TestInfo` for a given float and generator, then add it to a list. - fn register<F: Float, G: Generator<F>>(v: &mut Vec<Self>) { - let f_name = type_name::<F>(); - let gen_name = G::NAME; - let gen_short_name = G::SHORT_NAME; - let name = format!("{f_name} {gen_name}"); - let short_name = format!("{f_name} {gen_short_name}"); - let short_name_padded = format!("{short_name:18}"); - - let info = TestInfo { - float_name: f_name, - float_bits: F::BITS, - gen_name, - progress: None, - name, - short_name_padded, - short_name, - launch: test_runner::<F, G>, - total_tests: G::total_tests(), - completed: OnceLock::new(), - }; - v.push(info); - } - - /// True if this should be run after all others. - fn is_huge_test(&self) -> bool { - self.total_tests >= HUGE_TEST_CUTOFF - } - - /// When the test is finished, update progress bar messages and finalize. - fn complete(&self, c: Completed) { - self.progress.as_ref().unwrap().complete(&c, 0); - self.completed.set(c).unwrap(); - } -} - -/// Result of an input did not parsing successfully. -#[derive(Clone, Debug)] -enum CheckFailure { - /// Above the zero cutoff but got rounded to zero. - UnexpectedZero, - /// Below the infinity cutoff but got rounded to infinity. - UnexpectedInf, - /// Above the negative infinity cutoff but got rounded to negative infinity. - UnexpectedNegInf, - /// Got a `NaN` when none was expected. - UnexpectedNan, - /// Expected `NaN`, got none. - ExpectedNan, - /// Expected infinity, got finite. - ExpectedInf, - /// Expected negative infinity, got finite. - ExpectedNegInf, - /// The value exceeded its error tolerance. - InvalidReal { - /// Error from the expected value, as a float. - error_float: Option<f64>, - /// Error as a rational string (since it can't always be represented as a float). - error_str: Box<str>, - /// True if the error was caused by not rounding to even at the midpoint between - /// two representable values. - incorrect_midpoint_rounding: bool, - }, - /// String did not parse successfully. - ParsingFailed(Box<str>), - /// A panic was caught. - Panic(Box<str>), -} - -impl fmt::Display for CheckFailure { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CheckFailure::UnexpectedZero => { - write!(f, "incorrectly rounded to 0 (expected nonzero)") - } - CheckFailure::UnexpectedInf => { - write!(f, "incorrectly rounded to +inf (expected finite)") - } - CheckFailure::UnexpectedNegInf => { - write!(f, "incorrectly rounded to -inf (expected finite)") - } - CheckFailure::UnexpectedNan => write!(f, "got a NaN where none was expected"), - CheckFailure::ExpectedNan => write!(f, "expected a NaN but did not get it"), - CheckFailure::ExpectedInf => write!(f, "expected +inf but did not get it"), - CheckFailure::ExpectedNegInf => write!(f, "expected -inf but did not get it"), - CheckFailure::InvalidReal { error_float, error_str, incorrect_midpoint_rounding } => { - if *incorrect_midpoint_rounding { - write!( - f, - "midpoint between two representable values did not correctly \ - round to even; error: {error_str}" - )?; - } else { - write!(f, "real number did not parse correctly; error: {error_str}")?; - } - - if let Some(float) = error_float { - write!(f, " ({float})")?; - } - Ok(()) - } - CheckFailure::ParsingFailed(e) => write!(f, "parsing failed: {e}"), - CheckFailure::Panic(e) => write!(f, "function panicked: {e}"), - } - } -} - -/// Information about a completed test generator. -#[derive(Clone, Debug)] -struct Completed { - /// Finished tests (both successful and failed). - executed: u64, - /// Failed tests. - failures: u64, - /// Extra exit information if unsuccessful. - result: Result<FinishedAll, EarlyExit>, - /// If there is something to warn about (e.g bad estimate), leave it here. - warning: Option<Box<str>>, - /// Total time to run the test. - elapsed: Duration, -} - -/// Marker for completing all tests (used in `Result` types). -#[derive(Clone, Debug)] -struct FinishedAll; - -/// Reasons for exiting early. -#[derive(Clone, Debug)] -enum EarlyExit { - Timeout, - MaxFailures, -} - -/// Run all tests in `tests`. -/// -/// This launches a main thread that receives messages and handlees UI updates, and uses the -/// rest of the thread pool to execute the tests. -fn launch_tests(tests: &mut [TestInfo], cfg: &Config) -> Duration { - // Run shorter tests and smaller float types first. - tests.sort_unstable_by_key(|test| (test.total_tests, test.float_bits)); - - for test in tests.iter() { - println!("Launching test '{}'", test.name); - } - - let mut all_progress_bars = Vec::new(); - let start = Instant::now(); - - for test in tests.iter_mut() { - test.progress = Some(ui::Progress::new(test, &mut all_progress_bars)); - ui::set_panic_hook(&all_progress_bars); - ((test.launch)(test, cfg)); - } - - start.elapsed() -} - -/// Test runer for a single generator. -/// -/// This calls the generator's iterator multiple times (in parallel) and validates each output. -fn test_runner<F: Float, G: Generator<F>>(test: &TestInfo, cfg: &Config) { - let gen_ = G::new(); - let executed = AtomicU64::new(0); - let failures = AtomicU64::new(0); - - let checks_per_update = min(test.total_tests, 1000); - let started = Instant::now(); - - // Function to execute for a single test iteration. - let check_one = |buf: &mut String, ctx: G::WriteCtx| { - let executed = executed.fetch_add(1, Ordering::Relaxed); - buf.clear(); - G::write_string(buf, ctx); - - match validate::validate::<F>(buf) { - Ok(()) => (), - Err(e) => { - let CheckError { fail, input, float_res } = e; - test.progress.as_ref().unwrap().println(&format!( - "Failure in '{}': {fail}. parsing '{input}'. Parsed as: {float_res}", - test.name - )); - - let f = failures.fetch_add(1, Ordering::Relaxed); - // End early if the limit is exceeded. - if f >= cfg.max_failures { - return Err(EarlyExit::MaxFailures); - } - } - }; - - // Send periodic updates - if executed % checks_per_update == 0 { - let failures = failures.load(Ordering::Relaxed); - test.progress.as_ref().unwrap().update(executed, failures); - if started.elapsed() > cfg.timeout { - return Err(EarlyExit::Timeout); - } - } - - Ok(()) - }; - - // Run the test iterations in parallel. Each thread gets a string buffer to write - // its check values to. - let res = gen_.par_bridge().try_for_each_init(String::new, check_one); - - let elapsed = started.elapsed(); - let executed = executed.into_inner(); - let failures = failures.into_inner(); - - // Warn about bad estimates if relevant. - let warning = if executed != test.total_tests && res.is_ok() { - let msg = format!( - "executed tests != estimated ({executed} != {}) for {}", - test.total_tests, - G::NAME - ); - - Some(msg.into()) - } else { - None - }; - - let result = res.map(|()| FinishedAll); - test.complete(Completed { executed, failures, result, warning, elapsed }); -} diff --git a/src/etc/test-float-parse/src/main.rs b/src/etc/test-float-parse/src/main.rs deleted file mode 100644 index 9c6cad7324f..00000000000 --- a/src/etc/test-float-parse/src/main.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::process::ExitCode; -use std::time::Duration; - -use test_float_parse as tfp; - -static HELP: &str = r#"Usage: - - ./test-float-parse [--timeout x] [--exclude x] [--max-failures x] [INCLUDE ...] - ./test-float-parse [--fuzz-count x] [INCLUDE ...] - ./test-float-parse [--skip-huge] [INCLUDE ...] - ./test-float-parse --list - -Args: - - INCLUDE Include only tests with names containing these - strings. If this argument is not specified, all tests - are run. - --timeout N Exit after this amount of time (in seconds). - --exclude FILTER Skip tests containing this string. May be specified - more than once. - --list List available tests. - --max-failures N Limit to N failures per test. Defaults to 20. Pass - "--max-failures none" to remove this limit. - --fuzz-count N Run the fuzzer with N iterations. Only has an effect - if fuzz tests are enabled. Pass `--fuzz-count none` - to remove this limit. - --skip-huge Skip tests that run for a long time. - --all Reset previous `--exclude`, `--skip-huge`, and - `INCLUDE` arguments (useful for running all tests - via `./x`). -"#; - -enum ArgMode { - Any, - Timeout, - Exclude, - FuzzCount, - MaxFailures, -} - -fn main() -> ExitCode { - if cfg!(debug_assertions) { - println!( - "WARNING: running in debug mode. Release mode is recommended to reduce test duration." - ); - std::thread::sleep(Duration::from_secs(2)); - } - - let args: Vec<_> = std::env::args().skip(1).collect(); - if args.iter().any(|arg| arg == "--help" || arg == "-h") { - println!("{HELP}"); - return ExitCode::SUCCESS; - } - - if args.iter().any(|arg| arg == "--list") { - let tests = tfp::register_tests(&tfp::Config::default()); - println!("Available tests:"); - for t in tests { - println!("{}", t.name); - } - - return ExitCode::SUCCESS; - } - - let (cfg, include, exclude) = parse_args(args); - - tfp::run(cfg, &include, &exclude) -} - -/// Simple command argument parser -fn parse_args(args: Vec<String>) -> (tfp::Config, Vec<String>, Vec<String>) { - let mut cfg = tfp::Config::default(); - - let mut mode = ArgMode::Any; - let mut include = Vec::new(); - let mut exclude = Vec::new(); - - for arg in args { - mode = match mode { - ArgMode::Any if arg == "--timeout" => ArgMode::Timeout, - ArgMode::Any if arg == "--exclude" => ArgMode::Exclude, - ArgMode::Any if arg == "--max-failures" => ArgMode::MaxFailures, - ArgMode::Any if arg == "--fuzz-count" => ArgMode::FuzzCount, - ArgMode::Any if arg == "--skip-huge" => { - cfg.skip_huge = true; - ArgMode::Any - } - ArgMode::Any if arg == "--all" => { - cfg.skip_huge = false; - include.clear(); - exclude.clear(); - ArgMode::Any - } - ArgMode::Any if arg.starts_with('-') => { - panic!("Unknown argument {arg}. Usage:\n{HELP}") - } - ArgMode::Any => { - include.push(arg); - ArgMode::Any - } - ArgMode::Timeout => { - cfg.timeout = Duration::from_secs(arg.parse().unwrap()); - ArgMode::Any - } - ArgMode::MaxFailures => { - if arg == "none" { - cfg.disable_max_failures = true; - } else { - cfg.max_failures = arg.parse().unwrap(); - } - ArgMode::Any - } - ArgMode::FuzzCount => { - if arg == "none" { - cfg.fuzz_count = Some(u64::MAX); - } else { - cfg.fuzz_count = Some(arg.parse().unwrap()); - } - ArgMode::Any - } - ArgMode::Exclude => { - exclude.push(arg); - ArgMode::Any - } - } - } - - (cfg, include, exclude) -} diff --git a/src/etc/test-float-parse/src/traits.rs b/src/etc/test-float-parse/src/traits.rs deleted file mode 100644 index 16484f8fe2c..00000000000 --- a/src/etc/test-float-parse/src/traits.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! Interfaces used throughout this crate. - -use std::str::FromStr; -use std::{fmt, ops}; - -use num::Integer; -use num::bigint::ToBigInt; - -use crate::validate::Constants; - -/// Integer types. -#[allow(dead_code)] // Some functions only used for testing -pub trait Int: - Clone - + Copy - + fmt::Debug - + fmt::Display - + fmt::LowerHex - + ops::Add<Output = Self> - + ops::Sub<Output = Self> - + ops::Shl<u32, Output = Self> - + ops::Shr<u32, Output = Self> - + ops::BitAnd<Output = Self> - + ops::BitOr<Output = Self> - + ops::Not<Output = Self> - + ops::AddAssign - + ops::BitAndAssign - + ops::BitOrAssign - + From<u8> - + TryFrom<i8> - + TryFrom<u32, Error: fmt::Debug> - + TryFrom<u64, Error: fmt::Debug> - + TryFrom<u128, Error: fmt::Debug> - + TryInto<u64, Error: fmt::Debug> - + TryInto<u32, Error: fmt::Debug> - + ToBigInt - + PartialOrd - + Integer - + Send - + 'static -{ - type Signed: Int; - type Bytes: Default + AsMut<[u8]>; - - const BITS: u32; - const ZERO: Self; - const ONE: Self; - const MAX: Self; - - fn to_signed(self) -> Self::Signed; - fn wrapping_neg(self) -> Self; - fn trailing_zeros(self) -> u32; - - fn hex(self) -> String { - format!("{self:x}") - } -} - -macro_rules! impl_int { - ($($uty:ty, $sty:ty);+) => { - $( - impl Int for $uty { - type Signed = $sty; - type Bytes = [u8; Self::BITS as usize / 8]; - const BITS: u32 = Self::BITS; - const ZERO: Self = 0; - const ONE: Self = 1; - const MAX: Self = Self::MAX; - fn to_signed(self) -> Self::Signed { - self.try_into().unwrap() - } - fn wrapping_neg(self) -> Self { - self.wrapping_neg() - } - fn trailing_zeros(self) -> u32 { - self.trailing_zeros() - } - } - - impl Int for $sty { - type Signed = Self; - type Bytes = [u8; Self::BITS as usize / 8]; - const BITS: u32 = Self::BITS; - const ZERO: Self = 0; - const ONE: Self = 1; - const MAX: Self = Self::MAX; - fn to_signed(self) -> Self::Signed { - self - } - fn wrapping_neg(self) -> Self { - self.wrapping_neg() - } - fn trailing_zeros(self) -> u32 { - self.trailing_zeros() - } - } - )+ - } -} - -impl_int!(u16, i16; u32, i32; u64, i64); - -/// Floating point types. -pub trait Float: - Copy + fmt::Debug + fmt::LowerExp + FromStr<Err: fmt::Display> + Sized + Send + 'static -{ - /// Unsigned integer of same width - type Int: Int<Signed = Self::SInt>; - type SInt: Int; - - /// Total bits - const BITS: u32; - - /// (Stored) bits in the mantissa) - const MAN_BITS: u32; - - /// Bits in the exponent - const EXP_BITS: u32 = Self::BITS - Self::MAN_BITS - 1; - - /// A saturated exponent (all ones) - const EXP_SAT: u32 = (1 << Self::EXP_BITS) - 1; - - /// The exponent bias, also its maximum value - const EXP_BIAS: u32 = Self::EXP_SAT >> 1; - - const MAN_MASK: Self::Int; - const SIGN_MASK: Self::Int; - - fn from_bits(i: Self::Int) -> Self; - fn to_bits(self) -> Self::Int; - - /// Rational constants associated with this float type. - fn constants() -> &'static Constants; - - fn is_sign_negative(self) -> bool { - (self.to_bits() & Self::SIGN_MASK) > Self::Int::ZERO - } - - /// Exponent without adjustment for bias. - fn exponent(self) -> u32 { - ((self.to_bits() >> Self::MAN_BITS) & Self::EXP_SAT.try_into().unwrap()).try_into().unwrap() - } - - fn mantissa(self) -> Self::Int { - self.to_bits() & Self::MAN_MASK - } -} - -macro_rules! impl_float { - ($($fty:ty, $ity:ty);+) => { - $( - impl Float for $fty { - type Int = $ity; - type SInt = <Self::Int as Int>::Signed; - const BITS: u32 = <$ity>::BITS; - const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1; - const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE; - const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1); - fn from_bits(i: Self::Int) -> Self { Self::from_bits(i) } - fn to_bits(self) -> Self::Int { self.to_bits() } - fn constants() -> &'static Constants { - use std::sync::LazyLock; - static CONSTANTS: LazyLock<Constants> = LazyLock::new(Constants::new::<$fty>); - &CONSTANTS - } - } - )+ - } -} - -impl_float!(f32, u32; f64, u64); - -#[cfg(not(bootstrap))] -#[cfg(target_has_reliable_f16)] -impl_float!(f16, u16); - -/// A test generator. Should provide an iterator that produces unique patterns to parse. -/// -/// The iterator needs to provide a `WriteCtx` (could be anything), which is then used to -/// write the string at a later step. This is done separately so that we can reuse string -/// allocations (which otherwise turn out to be a pretty expensive part of these tests). -pub trait Generator<F: Float>: Iterator<Item = Self::WriteCtx> + Send + 'static { - /// Full display and filtering name - const NAME: &'static str = Self::SHORT_NAME; - - /// Name for display with the progress bar - const SHORT_NAME: &'static str; - - /// The context needed to create a test string. - type WriteCtx: Send; - - /// Number of tests that will be run. - fn total_tests() -> u64; - - /// Constructor for this test generator. - fn new() -> Self; - - /// Create a test string given write context, which was produced as a step from the iterator. - /// - /// `s` will be provided empty. - fn write_string(s: &mut String, ctx: Self::WriteCtx); -} - -/// For tests that use iterator combinators, it is easier to just to box the iterator than trying -/// to specify its type. This is a shorthand for the usual type. -pub type BoxGenIter<This, F> = Box<dyn Iterator<Item = <This as Generator<F>>::WriteCtx> + Send>; diff --git a/src/etc/test-float-parse/src/ui.rs b/src/etc/test-float-parse/src/ui.rs deleted file mode 100644 index 73473eef0bf..00000000000 --- a/src/etc/test-float-parse/src/ui.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Progress bars and such. - -use std::any::type_name; -use std::fmt; -use std::io::{self, Write}; -use std::process::ExitCode; -use std::time::Duration; - -use indicatif::{ProgressBar, ProgressStyle}; - -use crate::{Completed, Config, EarlyExit, FinishedAll, TestInfo}; - -/// Templates for progress bars. -const PB_TEMPLATE: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \ - {human_pos:>8}/{human_len:8} {msg} f {per_sec:14} eta {eta:8}"; -const PB_TEMPLATE_FINAL: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \ - {human_pos:>8}/{human_len:8} {msg:.COLOR} {per_sec:18} {elapsed_precise}"; - -/// Thin abstraction over our usage of a `ProgressBar`. -#[derive(Debug)] -pub struct Progress { - pb: ProgressBar, - make_final_style: NoDebug<Box<dyn Fn(&'static str) -> ProgressStyle + Sync>>, -} - -impl Progress { - /// Create a new progress bar within a multiprogress bar. - pub fn new(test: &TestInfo, all_bars: &mut Vec<ProgressBar>) -> Self { - let initial_template = PB_TEMPLATE.replace("NAME", &test.short_name_padded); - let final_template = PB_TEMPLATE_FINAL.replace("NAME", &test.short_name_padded); - let initial_style = - ProgressStyle::with_template(&initial_template).unwrap().progress_chars("##-"); - let make_final_style = move |color| { - ProgressStyle::with_template(&final_template.replace("COLOR", color)) - .unwrap() - .progress_chars("##-") - }; - - let pb = ProgressBar::new(test.total_tests); - pb.set_style(initial_style); - pb.set_length(test.total_tests); - pb.set_message("0"); - all_bars.push(pb.clone()); - - Progress { pb, make_final_style: NoDebug(Box::new(make_final_style)) } - } - - /// Completed a out of b tests. - pub fn update(&self, completed: u64, failures: u64) { - // Infrequently update the progress bar. - if completed % 5_000 == 0 || failures > 0 { - self.pb.set_position(completed); - } - - if failures > 0 { - self.pb.set_message(format! {"{failures}"}); - } - } - - /// Finalize the progress bar. - pub fn complete(&self, c: &Completed, real_total: u64) { - let f = c.failures; - let (color, msg, finish_fn): (&str, String, fn(&ProgressBar)) = match &c.result { - Ok(FinishedAll) if f > 0 => { - ("red", format!("{f} f (completed with errors)",), ProgressBar::finish) - } - Ok(FinishedAll) => { - ("green", format!("{f} f (completed successfully)",), ProgressBar::finish) - } - Err(EarlyExit::Timeout) => ("red", format!("{f} f (timed out)"), ProgressBar::abandon), - Err(EarlyExit::MaxFailures) => { - ("red", format!("{f} f (failure limit)"), ProgressBar::abandon) - } - }; - - self.pb.set_position(real_total); - self.pb.set_style(self.make_final_style.0(color)); - self.pb.set_message(msg); - finish_fn(&self.pb); - } - - /// Print a message to stdout above the current progress bar. - pub fn println(&self, msg: &str) { - self.pb.suspend(|| println!("{msg}")); - } -} - -/// Print final messages after all tests are complete. -pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> ExitCode { - println!("\n\nResults:"); - - let mut failed_generators = 0; - let mut stopped_generators = 0; - - for t in tests { - let Completed { executed, failures, elapsed, warning, result } = t.completed.get().unwrap(); - - let stat = if result.is_err() { - stopped_generators += 1; - "STOPPED" - } else if *failures > 0 { - failed_generators += 1; - "FAILURE" - } else { - "SUCCESS" - }; - - println!( - " {stat} for generator '{name}'. {passed}/{executed} passed in {elapsed:?}", - name = t.name, - passed = executed - failures, - ); - - if let Some(warning) = warning { - println!(" warning: {warning}"); - } - - match result { - Ok(FinishedAll) => (), - Err(EarlyExit::Timeout) => { - println!(" exited early; exceded {:?} timeout", cfg.timeout) - } - Err(EarlyExit::MaxFailures) => { - println!(" exited early; exceeded {:?} max failures", cfg.max_failures) - } - } - } - - println!( - "{passed}/{} tests succeeded in {total_elapsed:?} ({passed} passed, {} failed, {} stopped)", - tests.len(), - failed_generators, - stopped_generators, - passed = tests.len() - failed_generators - stopped_generators, - ); - - if failed_generators > 0 || stopped_generators > 0 { - ExitCode::FAILURE - } else { - ExitCode::SUCCESS - } -} - -/// indicatif likes to eat panic messages. This workaround isn't ideal, but it improves things. -/// <https://github.com/console-rs/indicatif/issues/121>. -pub fn set_panic_hook(drop_bars: &[ProgressBar]) { - let hook = std::panic::take_hook(); - let drop_bars = drop_bars.to_owned(); - std::panic::set_hook(Box::new(move |info| { - for bar in &drop_bars { - bar.abandon(); - println!(); - io::stdout().flush().unwrap(); - io::stderr().flush().unwrap(); - } - hook(info); - })); -} - -/// Allow non-Debug items in a `derive(Debug)` struct. -#[derive(Clone)] -struct NoDebug<T>(T); - -impl<T> fmt::Debug for NoDebug<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(type_name::<Self>()) - } -} diff --git a/src/etc/test-float-parse/src/validate.rs b/src/etc/test-float-parse/src/validate.rs deleted file mode 100644 index 40dda274e3b..00000000000 --- a/src/etc/test-float-parse/src/validate.rs +++ /dev/null @@ -1,394 +0,0 @@ -//! Everything related to verifying that parsed outputs are correct. - -use std::any::{Any, type_name}; -use std::collections::BTreeMap; -use std::ops::RangeInclusive; -use std::str::FromStr; -use std::sync::LazyLock; - -use num::bigint::ToBigInt; -use num::{BigInt, BigRational, FromPrimitive, Signed, ToPrimitive}; - -use crate::{CheckFailure, Float, Int}; - -/// Powers of two that we store for constants. Account for binary128 which has a 15-bit exponent. -const POWERS_OF_TWO_RANGE: RangeInclusive<i32> = (-(2 << 15))..=(2 << 15); - -/// Powers of ten that we cache. Account for binary128, which can fit +4932/-4931 -const POWERS_OF_TEN_RANGE: RangeInclusive<i32> = -5_000..=5_000; - -/// Cached powers of 10 so we can look them up rather than recreating. -static POWERS_OF_TEN: LazyLock<BTreeMap<i32, BigRational>> = LazyLock::new(|| { - POWERS_OF_TEN_RANGE.map(|exp| (exp, BigRational::from_u32(10).unwrap().pow(exp))).collect() -}); - -/// Rational property-related constants for a specific float type. -#[allow(dead_code)] -#[derive(Debug)] -pub struct Constants { - /// The minimum positive value (a subnormal). - min_subnormal: BigRational, - /// The maximum possible finite value. - max: BigRational, - /// Cutoff between rounding to zero and rounding to the minimum value (min subnormal). - zero_cutoff: BigRational, - /// Cutoff between rounding to the max value and rounding to infinity. - inf_cutoff: BigRational, - /// Opposite of `inf_cutoff` - neg_inf_cutoff: BigRational, - /// The powers of two for all relevant integers. - powers_of_two: BTreeMap<i32, BigRational>, - /// Half of each power of two. ULP = "unit in last position". - /// - /// This is a mapping from integers to half the precision available at that exponent. In other - /// words, `0.5 * 2^n` = `2^(n-1)`, which is half the distance between `m * 2^n` and - /// `(m + 1) * 2^n`, m ∈ ℤ. - /// - /// So, this is the maximum error from a real number to its floating point representation, - /// assuming the float type can represent the exponent. - half_ulp: BTreeMap<i32, BigRational>, - /// Handy to have around so we don't need to reallocate for it - two: BigInt, -} - -impl Constants { - pub fn new<F: Float>() -> Self { - let two_int = &BigInt::from_u32(2).unwrap(); - let two = &BigRational::from_integer(2.into()); - - // The minimum subnormal (aka minimum positive) value. Most negative power of two is the - // minimum exponent (bias - 1) plus the extra from shifting within the mantissa bits. - let min_subnormal = two.pow(-(F::EXP_BIAS + F::MAN_BITS - 1).to_signed()); - - // The maximum value is the maximum exponent with a fully saturated mantissa. This - // is easiest to calculate by evaluating what the next value up would be if representable - // (zeroed mantissa, exponent increments by one, i.e. `2^(bias + 1)`), and subtracting - // a single LSB (`2 ^ (-mantissa_bits)`). - let max = (two - two.pow(-F::MAN_BITS.to_signed())) * (two.pow(F::EXP_BIAS.to_signed())); - let zero_cutoff = &min_subnormal / two_int; - - let inf_cutoff = &max + two_int.pow(F::EXP_BIAS - F::MAN_BITS - 1); - let neg_inf_cutoff = -&inf_cutoff; - - let powers_of_two: BTreeMap<i32, _> = - (POWERS_OF_TWO_RANGE).map(|n| (n, two.pow(n))).collect(); - let mut half_ulp = powers_of_two.clone(); - half_ulp.iter_mut().for_each(|(_k, v)| *v = &*v / two_int); - - Self { - min_subnormal, - max, - zero_cutoff, - inf_cutoff, - neg_inf_cutoff, - powers_of_two, - half_ulp, - two: two_int.clone(), - } - } -} - -/// Validate that a string parses correctly -pub fn validate<F: Float>(input: &str) -> Result<(), CheckError> { - // Catch panics in case debug assertions within `std` fail. - let parsed = std::panic::catch_unwind(|| { - input.parse::<F>().map_err(|e| CheckError { - fail: CheckFailure::ParsingFailed(e.to_string().into()), - input: input.into(), - float_res: "none".into(), - }) - }) - .map_err(|e| convert_panic_error(&e, input))??; - - // Parsed float, decoded into significand and exponent - let decoded = decode(parsed); - - // Float parsed separately into a rational - let rational = Rational::parse(input); - - // Verify that the values match - decoded.check(rational, input) -} - -/// Turn panics into concrete error types. -fn convert_panic_error(e: &dyn Any, input: &str) -> CheckError { - let msg = e - .downcast_ref::<String>() - .map(|s| s.as_str()) - .or_else(|| e.downcast_ref::<&str>().copied()) - .unwrap_or("(no contents)"); - - CheckError { - fail: CheckFailure::Panic(msg.into()), - input: input.into(), - float_res: "none".into(), - } -} - -/// The result of parsing a string to a float type. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum FloatRes<F: Float> { - Inf, - NegInf, - Zero, - Nan, - /// A real number with significand and exponent. Value is `sig * 2 ^ exp`. - Real { - sig: F::SInt, - exp: i32, - }, -} - -#[derive(Clone, Debug)] -pub struct CheckError { - pub fail: CheckFailure, - /// String for which parsing was attempted. - pub input: Box<str>, - /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here. - pub float_res: Box<str>, -} - -impl<F: Float> FloatRes<F> { - /// Given a known exact rational, check that this representation is accurate within the - /// limits of the float representation. If not, construct a failure `Update` to send. - fn check(self, expected: Rational, input: &str) -> Result<(), CheckError> { - let consts = F::constants(); - // let bool_helper = |cond: bool, err| cond.then_some(()).ok_or(err); - - let res = match (expected, self) { - // Easy correct cases - (Rational::Inf, FloatRes::Inf) - | (Rational::NegInf, FloatRes::NegInf) - | (Rational::Nan, FloatRes::Nan) => Ok(()), - - // Easy incorrect cases - ( - Rational::Inf, - FloatRes::NegInf | FloatRes::Zero | FloatRes::Nan | FloatRes::Real { .. }, - ) => Err(CheckFailure::ExpectedInf), - ( - Rational::NegInf, - FloatRes::Inf | FloatRes::Zero | FloatRes::Nan | FloatRes::Real { .. }, - ) => Err(CheckFailure::ExpectedNegInf), - ( - Rational::Nan, - FloatRes::Inf | FloatRes::NegInf | FloatRes::Zero | FloatRes::Real { .. }, - ) => Err(CheckFailure::ExpectedNan), - (Rational::Finite(_), FloatRes::Nan) => Err(CheckFailure::UnexpectedNan), - - // Cases near limits - (Rational::Finite(r), FloatRes::Zero) => { - if r <= consts.zero_cutoff { - Ok(()) - } else { - Err(CheckFailure::UnexpectedZero) - } - } - (Rational::Finite(r), FloatRes::Inf) => { - if r >= consts.inf_cutoff { - Ok(()) - } else { - Err(CheckFailure::UnexpectedInf) - } - } - (Rational::Finite(r), FloatRes::NegInf) => { - if r <= consts.neg_inf_cutoff { - Ok(()) - } else { - Err(CheckFailure::UnexpectedNegInf) - } - } - - // Actual numbers - (Rational::Finite(r), FloatRes::Real { sig, exp }) => Self::validate_real(r, sig, exp), - }; - - res.map_err(|fail| CheckError { - fail, - input: input.into(), - float_res: format!("{self:?}").into(), - }) - } - - /// Check that `sig * 2^exp` is the same as `rational`, within the float's error margin. - fn validate_real(rational: BigRational, sig: F::SInt, exp: i32) -> Result<(), CheckFailure> { - let consts = F::constants(); - - // `2^exp`. Use cached powers of two to be faster. - let two_exp = consts - .powers_of_two - .get(&exp) - .unwrap_or_else(|| panic!("missing exponent {exp} for {}", type_name::<F>())); - - // Rational from the parsed value, `sig * 2^exp` - let parsed_rational = two_exp * sig.to_bigint().unwrap(); - let error = (parsed_rational - &rational).abs(); - - // Determine acceptable error at this exponent, which is halfway between this value - // (`sig * 2^exp`) and the next value up (`(sig+1) * 2^exp`). - let half_ulp = consts.half_ulp.get(&exp).unwrap(); - - // If we are within one error value (but not equal) then we rounded correctly. - if &error < half_ulp { - return Ok(()); - } - - // For values where we are exactly between two representable values, meaning that the error - // is exactly one half of the precision at that exponent, we need to round to an even - // binary value (i.e. mantissa ends in 0). - let incorrect_midpoint_rounding = if &error == half_ulp { - if sig & F::SInt::ONE == F::SInt::ZERO { - return Ok(()); - } - - // We rounded to odd rather than even; failing based on midpoint rounding. - true - } else { - // We are out of spec for some other reason. - false - }; - - let one_ulp = consts.half_ulp.get(&(exp + 1)).unwrap(); - assert_eq!(one_ulp, &(half_ulp * &consts.two), "ULP values are incorrect"); - - let relative_error = error / one_ulp; - - Err(CheckFailure::InvalidReal { - error_float: relative_error.to_f64(), - error_str: relative_error.to_string().into(), - incorrect_midpoint_rounding, - }) - } - - /// Remove trailing zeros in the significand and adjust the exponent - #[cfg(test)] - fn normalize(self) -> Self { - use std::cmp::min; - - match self { - Self::Real { sig, exp } => { - // If there are trailing zeroes, remove them and increment the exponent instead - let shift = min(sig.trailing_zeros(), exp.wrapping_neg().try_into().unwrap()); - Self::Real { sig: sig >> shift, exp: exp + i32::try_from(shift).unwrap() } - } - _ => self, - } - } -} - -/// Decompose a float into its integral components. This includes the implicit bit. -/// -/// If `allow_nan` is `false`, panic if `NaN` values are reached. -fn decode<F: Float>(f: F) -> FloatRes<F> { - let ione = F::SInt::ONE; - let izero = F::SInt::ZERO; - - let mut exponent_biased = f.exponent(); - let mut mantissa = f.mantissa().to_signed(); - - if exponent_biased == 0 { - if mantissa == izero { - return FloatRes::Zero; - } - - exponent_biased += 1; - } else if exponent_biased == F::EXP_SAT { - if mantissa != izero { - return FloatRes::Nan; - } - - if f.is_sign_negative() { - return FloatRes::NegInf; - } - - return FloatRes::Inf; - } else { - // Set implicit bit - mantissa |= ione << F::MAN_BITS; - } - - let mut exponent = i32::try_from(exponent_biased).unwrap(); - - // Adjust for bias and the rnage of the mantissa - exponent -= i32::try_from(F::EXP_BIAS + F::MAN_BITS).unwrap(); - - if f.is_sign_negative() { - mantissa = mantissa.wrapping_neg(); - } - - FloatRes::Real { sig: mantissa, exp: exponent } -} - -/// A rational or its unrepresentable values. -#[derive(Clone, Debug, PartialEq)] -enum Rational { - Inf, - NegInf, - Nan, - Finite(BigRational), -} - -impl Rational { - /// Turn a string into a rational. `None` if `NaN`. - fn parse(s: &str) -> Rational { - let mut s = s; // lifetime rules - - if s.strip_prefix('+').unwrap_or(s).eq_ignore_ascii_case("nan") - || s.eq_ignore_ascii_case("-nan") - { - return Rational::Nan; - } - - if s.strip_prefix('+').unwrap_or(s).eq_ignore_ascii_case("inf") { - return Rational::Inf; - } - - if s.eq_ignore_ascii_case("-inf") { - return Rational::NegInf; - } - - // Fast path; no decimals or exponents ot parse - if s.bytes().all(|b| b.is_ascii_digit() || b == b'-') { - return Rational::Finite(BigRational::from_str(s).unwrap()); - } - - let mut ten_exp: i32 = 0; - - // Remove and handle e.g. `e-4`, `e+10`, `e5` suffixes - if let Some(pos) = s.bytes().position(|b| b == b'e' || b == b'E') { - let (dec, exp) = s.split_at(pos); - s = dec; - ten_exp = exp[1..].parse().unwrap(); - } - - // Remove the decimal and instead change our exponent - // E.g. "12.3456" becomes "123456 * 10^-4" - let mut s_owned; - if let Some(pos) = s.bytes().position(|b| b == b'.') { - ten_exp = ten_exp.checked_sub((s.len() - pos - 1).try_into().unwrap()).unwrap(); - s_owned = s.to_owned(); - s_owned.remove(pos); - s = &s_owned; - } - - // let pow = BigRational::from_u32(10).unwrap().pow(ten_exp); - let pow = - POWERS_OF_TEN.get(&ten_exp).unwrap_or_else(|| panic!("missing power of ten {ten_exp}")); - let r = pow - * BigInt::from_str(s) - .unwrap_or_else(|e| panic!("`BigInt::from_str(\"{s}\")` failed with {e}")); - Rational::Finite(r) - } - - #[cfg(test)] - fn expect_finite(self) -> BigRational { - let Self::Finite(r) = self else { - panic!("got non rational: {self:?}"); - }; - - r - } -} - -#[cfg(test)] -mod tests; diff --git a/src/etc/test-float-parse/src/validate/tests.rs b/src/etc/test-float-parse/src/validate/tests.rs deleted file mode 100644 index ab0e7d8a7ba..00000000000 --- a/src/etc/test-float-parse/src/validate/tests.rs +++ /dev/null @@ -1,149 +0,0 @@ -use num::ToPrimitive; - -use super::*; - -#[test] -fn test_parse_rational() { - assert_eq!(Rational::parse("1234").expect_finite(), BigRational::new(1234.into(), 1.into())); - assert_eq!( - Rational::parse("-1234").expect_finite(), - BigRational::new((-1234).into(), 1.into()) - ); - assert_eq!(Rational::parse("1e+6").expect_finite(), BigRational::new(1000000.into(), 1.into())); - assert_eq!(Rational::parse("1e-6").expect_finite(), BigRational::new(1.into(), 1000000.into())); - assert_eq!( - Rational::parse("10.4e6").expect_finite(), - BigRational::new(10400000.into(), 1.into()) - ); - assert_eq!( - Rational::parse("10.4e+6").expect_finite(), - BigRational::new(10400000.into(), 1.into()) - ); - assert_eq!( - Rational::parse("10.4e-6").expect_finite(), - BigRational::new(13.into(), 1250000.into()) - ); - assert_eq!( - Rational::parse("10.4243566462342456234124").expect_finite(), - BigRational::new(104243566462342456234124_i128.into(), 10000000000000000000000_i128.into()) - ); - assert_eq!(Rational::parse("inf"), Rational::Inf); - assert_eq!(Rational::parse("+inf"), Rational::Inf); - assert_eq!(Rational::parse("-inf"), Rational::NegInf); - assert_eq!(Rational::parse("NaN"), Rational::Nan); -} - -#[test] -fn test_decode() { - assert_eq!(decode(0f32), FloatRes::Zero); - assert_eq!(decode(f32::INFINITY), FloatRes::Inf); - assert_eq!(decode(f32::NEG_INFINITY), FloatRes::NegInf); - assert_eq!(decode(1.0f32).normalize(), FloatRes::Real { sig: 1, exp: 0 }); - assert_eq!(decode(-1.0f32).normalize(), FloatRes::Real { sig: -1, exp: 0 }); - assert_eq!(decode(100.0f32).normalize(), FloatRes::Real { sig: 100, exp: 0 }); - assert_eq!(decode(100.5f32).normalize(), FloatRes::Real { sig: 201, exp: -1 }); - assert_eq!(decode(-4.004f32).normalize(), FloatRes::Real { sig: -8396997, exp: -21 }); - assert_eq!(decode(0.0004f32).normalize(), FloatRes::Real { sig: 13743895, exp: -35 }); - assert_eq!(decode(f32::from_bits(0x1)).normalize(), FloatRes::Real { sig: 1, exp: -149 }); -} - -#[test] -fn test_validate() { - validate::<f32>("0").unwrap(); - validate::<f32>("-0").unwrap(); - validate::<f32>("1").unwrap(); - validate::<f32>("-1").unwrap(); - validate::<f32>("1.1").unwrap(); - validate::<f32>("-1.1").unwrap(); - validate::<f32>("1e10").unwrap(); - validate::<f32>("1e1000").unwrap(); - validate::<f32>("-1e1000").unwrap(); - validate::<f32>("1e-1000").unwrap(); - validate::<f32>("-1e-1000").unwrap(); -} - -#[test] -fn test_validate_real() { - // Most of the arbitrary values come from checking against <http://weitz.de/ieee/>. - let r = &BigRational::from_float(10.0).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), 10, 0).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), 10, -1).unwrap_err(); - FloatRes::<f32>::validate_real(r.clone(), 10, 1).unwrap_err(); - - let r = &BigRational::from_float(0.25).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), 1, -2).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), 2, -2).unwrap_err(); - - let r = &BigRational::from_float(1234.5678).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), 0b100110100101001000101011, -13).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), 0b100110100101001000101010, -13).unwrap_err(); - FloatRes::<f32>::validate_real(r.clone(), 0b100110100101001000101100, -13).unwrap_err(); - - let r = &BigRational::from_float(-1234.5678).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), -0b100110100101001000101011, -13).unwrap(); - FloatRes::<f32>::validate_real(r.clone(), -0b100110100101001000101010, -13).unwrap_err(); - FloatRes::<f32>::validate_real(r.clone(), -0b100110100101001000101100, -13).unwrap_err(); -} - -#[test] -#[allow(unused)] -fn test_validate_real_rounding() { - // Check that we catch when values don't round to even. - - // For f32, the cutoff between 1.0 and the next value up (1.0000001) is - // 1.000000059604644775390625. Anything below it should round down, anything above it should - // round up, and the value itself should round _down_ because `1.0` has an even significand but - // 1.0000001 is odd. - let v1_low_down = Rational::parse("1.00000005960464477539062499999").expect_finite(); - let v1_mid_down = Rational::parse("1.000000059604644775390625").expect_finite(); - let v1_high_up = Rational::parse("1.00000005960464477539062500001").expect_finite(); - - let exp = -(f32::MAN_BITS as i32); - let v1_down_sig = 1 << f32::MAN_BITS; - let v1_up_sig = (1 << f32::MAN_BITS) | 0b1; - - FloatRes::<f32>::validate_real(v1_low_down.clone(), v1_down_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(v1_mid_down.clone(), v1_down_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(v1_high_up.clone(), v1_up_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(-v1_low_down.clone(), -v1_down_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(-v1_mid_down.clone(), -v1_down_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(-v1_high_up.clone(), -v1_up_sig, exp).unwrap(); - - // 1.000000178813934326171875 is between 1.0000001 and the next value up, 1.0000002. The middle - // value here should round _up_ since 1.0000002 has an even mantissa. - let v2_low_down = Rational::parse("1.00000017881393432617187499999").expect_finite(); - let v2_mid_up = Rational::parse("1.000000178813934326171875").expect_finite(); - let v2_high_up = Rational::parse("1.00000017881393432617187500001").expect_finite(); - - let v2_down_sig = v1_up_sig; - let v2_up_sig = (1 << f32::MAN_BITS) | 0b10; - - FloatRes::<f32>::validate_real(v2_low_down.clone(), v2_down_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(v2_mid_up.clone(), v2_up_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(v2_high_up.clone(), v2_up_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(-v2_low_down.clone(), -v2_down_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(-v2_mid_up.clone(), -v2_up_sig, exp).unwrap(); - FloatRes::<f32>::validate_real(-v2_high_up.clone(), -v2_up_sig, exp).unwrap(); - - // Rounding the wrong direction should error - for res in [ - FloatRes::<f32>::validate_real(v1_mid_down.clone(), v1_up_sig, exp), - FloatRes::<f32>::validate_real(v2_mid_up.clone(), v2_down_sig, exp), - FloatRes::<f32>::validate_real(-v1_mid_down.clone(), -v1_up_sig, exp), - FloatRes::<f32>::validate_real(-v2_mid_up.clone(), -v2_down_sig, exp), - ] { - let e = res.unwrap_err(); - let CheckFailure::InvalidReal { incorrect_midpoint_rounding: true, .. } = e else { - panic!("{e:?}"); - }; - } -} - -/// Just a quick check that the constants are what we expect. -#[test] -fn check_constants() { - assert_eq!(f32::constants().max.to_f32().unwrap(), f32::MAX); - assert_eq!(f32::constants().min_subnormal.to_f32().unwrap(), f32::from_bits(0x1)); - assert_eq!(f64::constants().max.to_f64().unwrap(), f64::MAX); - assert_eq!(f64::constants().min_subnormal.to_f64().unwrap(), f64::from_bits(0x1)); -} |
