Skip to content

Commit c451197

Browse files
committed
Update readme
1 parent 298b6de commit c451197

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

src/etc/test-float-parse/README.md

+34-12
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,40 @@
33
These are tests designed to test decimal to float conversions (`dec2flt`) used
44
by the standard library.
55

6-
Breakdown:
6+
The generators work as follows:
77

8-
- Generators (implement the `Generator` trait) provide test cases
9-
- We `.parse()` to the relevant float types and decompose it into its
10-
significand and its exponent.
11-
- Separately, we parse to an exact value with `BigRational`
12-
- Check sig + exp against `BigRational` math to make sure it is accurate within
13-
rounding, or correctly rounded to +/- inf.
14-
- `rayon` is awesome so this all gets parallelized nicely
8+
- Each generator is a struct that lives somewhere in the `gen` module. Usually
9+
it is generic over a float type.
10+
- These generators must implement `Iterator`, which should return a context
11+
type that can be used to construct a test string (but usually not the string
12+
itself).
13+
- They must also implement the `Generator` trait, which provides a method to
14+
write test context to a string as a test case, as well as some extra metadata.
15+
16+
The split between context generation and string construction is so that
17+
we can reuse string allocations.
18+
- Each generator gets registered once for each float type. All of these
19+
generators then get iterated, and each test case checked against the float
20+
type's parse implementation.
1521

16-
Some tests generate strings, others generate bit patterns. For those that
17-
generate bit patterns, we need to use float -> dec conversions so that also
18-
gets tested.
22+
Some tests produce decimal strings, others generate bit patterns that need
23+
to convert to the float type before printing to a string. For these, float to
24+
decimal (`flt2dec`) conversions get tested, if unintentionally.
1925

20-
todo
26+
For each test case, the following is done:
27+
28+
- The test string is parsed to the float type using the standard library's
29+
implementation.
30+
- The test string is parsed separately to a `BigRational`, which acts as a
31+
representation with infinite precision.
32+
- The rational value then gets checked that it is within the float's
33+
representable values (absolute value greater than the smallest number to
34+
round to zero, but less less than the first value to round to infinity). If
35+
these limits are exceeded, check that the parsed float reflects that.
36+
- For real nonzero numbers, the parsed float is converted into a
37+
rational using `significand * 2^exponent`. It is then checked against the
38+
actual rational value, and verified to be within half a bit's precision
39+
of the parsed value.
40+
41+
This is all highly parallelized with `rayon`; test generators can run in
42+
parallel, and their tests get chunked and run in parallel.

src/etc/test-float-parse/src/validate.rs

+21-7
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ pub struct Constants {
4040
powers_of_two: BTreeMap<i32, BigRational>,
4141
/// Half of each power of two. ULP = "unit in last position".
4242
///
43-
/// This is a mapping from integers to half of the precision that can be had at that
44-
/// exponent. That is, these values are in between values that can be represented with a
45-
/// certain exponent, assuming an integer significand (always true for float representations).
43+
/// This is a mapping from integers to half the precision available at that exponent. In other
44+
/// words, `0.5 * 2^n` = `2^(n-1)`, which is half the distance between `m * 2^n` and
45+
/// `(m + 1) * 2^n`, m ∈ ℤ.
46+
///
47+
/// So, this is the maximum error from a real number to its floating point representation,
48+
/// assuming the float type can represent the exponent.
4649
half_ulp: BTreeMap<i32, BigRational>,
4750
/// Handy to have around so we don't need to reallocate for it
4851
two: BigInt,
@@ -176,14 +179,25 @@ impl<F: Float> FloatRes<F> {
176179
/// Check that `sig * 2^exp` is the same as `rational`, within the float's error margin.
177180
fn validate_real(rational: BigRational, sig: F::SInt, exp: i32) -> Result<(), CheckFailure> {
178181
let consts = F::constants();
179-
// Rational from the parsed value (`sig * 2^exp`). Use cached powers of two to be a bit
180-
// faster.
181-
let parsed_rational = consts.powers_of_two.get(&exp).unwrap() * sig.to_bigint().unwrap();
182+
183+
// `2^exp`. Use cached powers of two to be faster.
184+
let two_exp = consts
185+
.powers_of_two
186+
.get(&exp)
187+
.unwrap_or_else(|| panic!("missing exponent {exp} for {}", type_name::<F>()));
188+
189+
// Rational from the parsed value, `sig * 2^exp`
190+
let parsed_rational = two_exp * sig.to_bigint().unwrap();
182191
let error = (parsed_rational - rational).abs();
183192

184-
// Determine acceptable error at this exponent
193+
// Determine acceptable error at this exponent, which is halfway between this value
194+
// (`sig * 2^exp`) and the next value up (`(sig+1) * 2^exp`).
185195
let half_ulp = consts.half_ulp.get(&exp).unwrap();
186196

197+
// Our real value is allowed to be exactly at the midpoint between two values, or closer
198+
// to the real value.
199+
// todo: this currently allows rounding either up or down at the midpoint.
200+
// Is this correct?
187201
if &error <= half_ulp {
188202
return Ok(());
189203
}

0 commit comments

Comments
 (0)