monkey_test/gen/
float.rs

1//! Module with generators for generic floating point values.
2//! For non-generic types `f64` and `f32`, see modules [crate::gen::f64]
3//! and [crate::gen::f32] instead, which are specializations of this module.
4
5use super::float_parts::FloatParts;
6use crate::gen;
7use crate::BoxGen;
8use crate::MapWithGen;
9use num_traits::Float;
10use rand::distributions::uniform::SampleUniform;
11use rand::Rng;
12use rand::SeedableRng;
13use std::fmt::Debug;
14use std::ops::Bound;
15use std::ops::RangeBounds;
16
17/// Generator that return any floating point value, including any
18/// finite number, NaN, Inf and -Inf.
19pub fn any<F>() -> BoxGen<F>
20where
21    F: Float + FloatParts + SampleUniform + 'static,
22{
23    let nans = gen::fixed::constant(F::nan());
24    gen::mix_with_ratio(&[(98, number()), (2, nans)])
25}
26
27/// Generator that only return finite numbers, `-Inf` and
28/// `Inf`. In other words any float value besides `NaN`.
29pub fn number<F>() -> BoxGen<F>
30where
31    F: Float + FloatParts + SampleUniform + 'static,
32{
33    let infs = gen::pick_evenly(&[F::neg_infinity(), F::infinity()]);
34    gen::mix_with_ratio(&[(98, finite()), (2, infs)])
35}
36
37/// Generator that only return numbers between 0 and `+Inf`.
38pub fn positive<F>() -> BoxGen<F>
39where
40    F: Float + FloatParts + SampleUniform + 'static,
41{
42    let infs = gen::fixed::constant(F::infinity());
43    let finites = ranged(F::zero()..=F::max_value());
44    gen::mix_with_ratio(&[(98, finites), (2, infs)])
45}
46
47/// Generator that only return numbers between `-Inf` and -0.
48pub fn negative<F>() -> BoxGen<F>
49where
50    F: Float + FloatParts + SampleUniform + 'static,
51{
52    let infs = gen::fixed::constant(F::neg_infinity());
53    let finites = ranged(F::min_value()..=F::neg_zero());
54    gen::mix_with_ratio(&[(98, finites), (2, infs)])
55}
56
57/// Generator that only return finite numbers between minimum
58/// and maximum finite number.
59pub fn finite<F>() -> BoxGen<F>
60where
61    F: Float + FloatParts + SampleUniform + 'static,
62{
63    ranged(F::min_value()..=F::max_value())
64}
65
66/// Generator that only return finite numbers in the range from 0
67/// (inclusive) to 1 (exclusive).
68pub fn zero_to_one<F>() -> BoxGen<F>
69where
70    F: Float + FloatParts + SampleUniform + 'static,
71{
72    ranged(F::zero()..F::one())
73}
74
75/// Generator that only return finite numbers in the given range.
76pub fn ranged<F, B>(bound: B) -> BoxGen<F>
77where
78    F: Float + FloatParts + SampleUniform + 'static,
79    B: RangeBounds<F>,
80{
81    let start: F = from_twos_complement_bits(start(&bound));
82    let end: F = from_twos_complement_bits(end(&bound));
83
84    check_bounds_are_finite(start, end);
85
86    let (start, end) = if start > end {
87        (end, start)
88    } else {
89        (start, end)
90    };
91
92    let all_special_values = [
93        F::zero(),
94        F::neg_zero(),
95        F::one(),
96        F::one().neg(),
97        F::min_value(),
98        F::max_value(),
99        F::min_positive_value(),
100        F::min_positive_value().neg(),
101        start,
102        end,
103    ];
104
105    let relevant_special_values = all_special_values
106        .iter()
107        .filter(|v| {
108            // special handling if one f bound extremes happen to be zero. We
109            // want to distinguish between -0 and +0.
110            let value_has_sign_within_range = v.is_sign_negative()
111                && start.is_sign_negative()
112                || v.is_sign_positive() && end.is_sign_positive();
113
114            let is_in_range = start <= **v && **v <= end;
115
116            is_in_range && value_has_sign_within_range
117        })
118        .cloned()
119        .collect::<Vec<_>>();
120
121    let special_values = gen::pick_evenly(&relevant_special_values);
122
123    gen::mix_with_ratio(&[
124        (90, completely_random_range(bound)),
125        (10, special_values),
126    ])
127}
128
129/// Float generator with completely random distribution. This function has a
130/// long name, since `ranged` should be preferred.
131pub fn completely_random_range<F, B>(bound: B) -> BoxGen<F>
132where
133    F: Float + FloatParts + SampleUniform + 'static,
134    B: RangeBounds<F>,
135{
136    let min = start(&bound);
137    let max = end(&bound);
138
139    check_bounds_are_finite::<F>(
140        from_twos_complement_bits(min),
141        from_twos_complement_bits(max),
142    );
143
144    let (min, max) = if min > max { (max, min) } else { (min, max) };
145
146    // Generate two's complement bits signed integer representation of finite
147    // floats
148    gen::from_fn(move |seed| {
149        //let distr = rand::distributions::Uniform::new_inclusive(min, max);
150        let mut x = rand_chacha::ChaCha8Rng::seed_from_u64(seed);
151
152        std::iter::from_fn(move || Some(x.gen_range(min..=max)))
153    })
154    // Map to actual floats from two's complement bits
155    .map(
156        |i| from_twos_complement_bits(i),
157        |f| to_twos_complement_bits(f),
158    )
159    .with_shrinker(crate::shrink::float())
160}
161
162fn check_bounds_are_finite<F>(start: F, end: F)
163where
164    F: Float + Debug + 'static,
165{
166    assert!(
167        start.is_finite(),
168        "Given range can not have non-finite value {start:?} as range start"
169    );
170    assert!(
171        end.is_finite(),
172        "Given range can not have non-finite value {end:?} as range end"
173    );
174}
175
176/// Convert floating point to signed bit representation
177fn to_twos_complement_bits<F>(f: F) -> i64
178where
179    F: Float + FloatParts,
180{
181    let sign_is_negative = f.is_sign_negative();
182    let exponent = f.exponent() as i64;
183    let fraction = f.fraction() as i64;
184
185    let unsigned_bits = (exponent << F::exponent_bit_position()) | fraction;
186
187    if sign_is_negative {
188        // special case for -0.0, which can not be represented by negation only,
189        // hence also subtracting one.
190        -unsigned_bits - 1
191    } else {
192        unsigned_bits
193    }
194}
195
196fn from_twos_complement_bits<F>(i: i64) -> F
197where
198    F: Float + FloatParts,
199{
200    let sign_is_negative = i < 0;
201    let unsigned_bits = if sign_is_negative { -i - 1 } else { i };
202
203    let fraction_mask = (1 << F::exponent_bit_position()) - 1;
204    let fraction = (unsigned_bits & fraction_mask) as u64;
205    let exponent = (unsigned_bits >> F::exponent_bit_position()) as u16;
206
207    F::from_bits(F::compose(sign_is_negative, exponent, fraction))
208}
209
210fn start<F, B>(bounds: &B) -> i64
211where
212    F: Float + FloatParts + Copy,
213    B: RangeBounds<F>,
214{
215    match bounds.start_bound() {
216        Bound::Included(x) => to_twos_complement_bits(*x),
217        Bound::Excluded(x) => to_twos_complement_bits(*x) + 1,
218        Bound::Unbounded => to_twos_complement_bits(F::min_value()),
219    }
220}
221
222fn end<F, B>(bounds: &B) -> i64
223where
224    F: Float + FloatParts + Copy,
225    B: RangeBounds<F>,
226{
227    match bounds.end_bound() {
228        Bound::Included(x) => to_twos_complement_bits(*x),
229        Bound::Excluded(x) => to_twos_complement_bits(*x) - 1,
230        Bound::Unbounded => to_twos_complement_bits(F::max_value()),
231    }
232}
233
234#[cfg(test)]
235mod test {
236    //! In many of the tests, we only test f32, not f64, since expecting the
237    //! behaviour to be type agnostic.
238
239    use super::from_twos_complement_bits;
240    use super::to_twos_complement_bits;
241    use crate::gen;
242    use crate::monkey_test;
243    use crate::testing::assert_generator_can_shrink;
244    use crate::BoxGen;
245    use std::ops::RangeBounds;
246
247    #[test]
248    fn convert_to_and_from_finite_f64() {
249        // For more info on the composition of double precision floats, see
250        // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
251        let exponent_max: i64 = 0x7fe; // 11 bits
252        let fraction_max: i64 = 0xF_FFFF_FFFF_FFFF; // 52 bits
253        let max = (exponent_max << 52) | fraction_max;
254        let min = -max;
255
256        monkey_test()
257            .with_generator(gen::i64::ranged(min..=max))
258            .assert_true(|i| {
259                let f: f64 = from_twos_complement_bits(i);
260                let j = to_twos_complement_bits(f);
261
262                i == j
263            });
264    }
265
266    #[test]
267    fn convert_to_and_from_finite_f32() {
268        // For more info on the composition of single precision floats, see
269        // https://en.wikipedia.org/wiki/Single-precision_floating-point_format
270        let exponent_max: i64 = 0xfe; // 8 bits
271        let fraction_max: i64 = 0x7f_ffff; // 23 bits
272        let max = (exponent_max << 23) | fraction_max;
273        let min = -max;
274
275        monkey_test()
276            .with_generator(gen::i64::ranged(min..=max))
277            .assert_true(|i| {
278                let f: f32 = from_twos_complement_bits(i);
279                let j = to_twos_complement_bits(f);
280
281                i == j
282            });
283    }
284
285    #[test]
286    fn sign_is_kept_on_zero_in_conversion() {
287        let original = -0.0;
288        let bits = to_twos_complement_bits(original);
289
290        let copy: f64 = from_twos_complement_bits(bits);
291        assert!(bits < 0, "negative sign is kept in bits representation");
292        assert!(
293            copy.is_sign_negative(),
294            "negative sign is kept in roundtrip float"
295        );
296        assert!(
297            copy == 0.0,
298            "value 0 is kept in roundtrip float, got {copy}"
299        );
300    }
301
302    #[test]
303    fn verify_generator_any() {
304        let gen = gen::f32::any();
305
306        assert_has_values(
307            gen,
308            &[
309                f32::NAN,
310                f32::MAX,
311                f32::MIN,
312                f32::INFINITY,
313                f32::NEG_INFINITY,
314                0.0,
315                -0.0,
316                1.0,
317                -1.0,
318            ],
319        );
320    }
321
322    #[test]
323    fn verify_generator_number() {
324        let gen = gen::f32::number();
325        assert_all_values_are_in_range(
326            gen.clone(),
327            f32::NEG_INFINITY..=f32::INFINITY,
328        );
329        assert_has_values(gen, &[0.0]);
330    }
331
332    #[test]
333    fn verify_generator_positive() {
334        let gen = gen::f32::positive();
335        assert_all_values_are_in_range(gen.clone(), 0.0..=f32::INFINITY);
336        assert_does_not_have_value(gen.clone(), -0.0);
337        assert_has_values(gen, &[0.0, 1.0, f32::MAX, f32::INFINITY]);
338    }
339
340    #[test]
341    fn verify_generator_negative() {
342        let gen = gen::f32::negative();
343        assert_all_values_are_in_range(gen.clone(), f32::NEG_INFINITY..=-0.0);
344        assert_does_not_have_value(gen.clone(), 0.0);
345        assert_has_values(gen, &[-0.0, -1.0, f32::MIN, f32::NEG_INFINITY]);
346    }
347
348    #[test]
349    fn verify_generator_finite() {
350        let gen = gen::f32::finite();
351        assert_all_values_are_in_range(gen.clone(), f32::MIN..=f32::MAX);
352        assert_has_values(gen, &[-0.0, -1.0, 0.0, 1.0, f32::MIN, f32::MAX]);
353    }
354
355    #[test]
356    fn verify_generator_ranged() {
357        // negative range
358        let gen = gen::f32::ranged(-555.0..=-72.0);
359        assert_all_values_are_in_range(gen.clone(), -555.0..=-72.0);
360        assert_has_values(gen, &[-555.0, -72.0]);
361
362        // positive range
363        let gen = gen::f32::ranged(72.0..=555.0);
364        assert_all_values_are_in_range(gen.clone(), 72.0..=555.0);
365        assert_has_values(gen, &[72.0, 555.0]);
366
367        // range inverted. lagest value first in range
368        let gen = gen::f32::ranged(555.0..=72.0);
369        assert_all_values_are_in_range(gen.clone(), 72.0..=555.0);
370        assert_has_values(gen, &[72.0, 555.0]);
371
372        // sign-straddling range
373        let gen = gen::f32::ranged(-555.0..=72.0);
374        assert_all_values_are_in_range(gen.clone(), -555.0..=72.0);
375        assert_has_values(gen, &[-555.0, 72.0, -0.0, 0.0, -1.0, 1.0]);
376    }
377
378    #[test]
379    fn verify_generator_zero_to_one() {
380        let gen = gen::f32::zero_to_one();
381        assert_all_values_are_in_range(gen.clone(), 0.0..1.0);
382        assert_has_values(gen.clone(), &[0.0]);
383        assert_does_not_have_value(gen, -0.0);
384    }
385
386    #[test]
387    #[should_panic]
388    fn let_ranged_panic_on_nan() {
389        gen::f32::ranged(f32::NAN..10.0);
390    }
391
392    #[test]
393    #[should_panic]
394    fn let_ranged_panic_on_neg_inf() {
395        gen::f32::ranged(f32::NEG_INFINITY..10.0);
396    }
397
398    #[test]
399    #[should_panic]
400    fn let_ranged_panic_on_pos_inf() {
401        gen::f32::ranged(10.0..=f32::INFINITY);
402    }
403
404    #[test]
405    #[should_panic]
406    fn let_completely_random_panic_on_nan() {
407        gen::f32::completely_random(f32::NAN..10.0);
408    }
409
410    #[test]
411    #[should_panic]
412    fn let_completely_random_panic_on_neg_inf() {
413        gen::f32::completely_random(f32::NEG_INFINITY..10.0);
414    }
415
416    #[test]
417    #[should_panic]
418    fn let_completely_random_panic_on_pos_inf() {
419        gen::f32::completely_random(10.0..=f32::INFINITY);
420    }
421
422    #[test]
423    fn should_have_shrinker() {
424        assert_generator_can_shrink(gen::f64::any(), std::f64::consts::PI)
425    }
426
427    fn assert_all_values_are_in_range<B>(gen: BoxGen<f32>, range: B)
428    where
429        B: RangeBounds<f32> + std::fmt::Debug,
430    {
431        gen.examples(crate::seed_to_use()).take(1000).for_each(|v| {
432            assert!(range.contains(&v), "Range {range:?} should contain {v}")
433        });
434    }
435
436    fn assert_has_values(gen: BoxGen<f32>, values: &[f32]) {
437        values.iter().for_each(|v| {
438            let examples_count = 1000;
439            let has_value =
440                gen.examples(crate::seed_to_use()).take(examples_count).any(|e| {
441                  float_equals(e, *v)
442                });
443
444            assert!(
445                has_value,
446                "Generator did not have {v} in the first {examples_count} examples."
447            );
448        });
449    }
450
451    fn assert_does_not_have_value(gen: BoxGen<f32>, value: f32) {
452        let examples_count = 500;
453        gen.examples(crate::seed_to_use())
454            .take(examples_count)
455            .for_each(|e| {
456                let r = float_equals(e, value);
457
458                println!("cmp: {e} == {value}: {r}");
459
460                assert!(
461                    !r,
462                    "Search for {value} in generator found {e} in \
463                    the first {examples_count} examples."
464                );
465            });
466    }
467
468    fn float_equals(a: f32, b: f32) -> bool {
469        // NaN never equals anything else, not even other NaN
470        let both_are_nan = a.is_nan() && b.is_nan();
471        // In these generators +0.0 and -0.0 are not regarded to be equal, even
472        // though for normal floats they are regarded to be equal.
473        let signums_are_the_same = a.signum() == b.signum();
474        // Exact comparison is used on purpose
475        both_are_nan || (signums_are_the_same && a == b)
476    }
477}