proptest/num/
float_samplers.rs

1//-
2// Copyright 2022 The proptest developers
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! Alternative uniform float samplers.
11//! These samplers are used over the ones from `rand` because the ones provided by the
12//! rand crate are prone to overflow. In addition, these are 'high precision' samplers
13//! that are more appropriate for test data.
14//! The samplers work by splitting the range into equally sized intervals and selecting
15//! an iterval at random. That interval is then itself split and a new interval is
16//! selected at random. The process repeats until the interval only contains two
17//! floating point values at the bounds. At that stage, one is selected at random and
18//! returned.
19
20pub(crate) use self::f32::F32U;
21pub(crate) use self::f64::F64U;
22
23macro_rules! float_sampler {
24    ($typ: ident, $int_typ: ident, $wrapper: ident) => {
25        pub mod $typ {
26            use rand::prelude::*;
27            use rand::distributions::uniform::{
28                SampleBorrow, SampleUniform, UniformSampler,
29            };
30            #[cfg(not(feature = "std"))]
31            use num_traits::float::Float;
32
33            #[must_use]
34            // Returns the previous float value. In other words the greatest value representable
35            // as a float such that `next_down(a) < a`. `-0.` is treated as `0.`.
36            fn next_down(a: $typ) -> $typ {
37                debug_assert!(a.is_finite() && a > $typ::MIN, "`next_down` invalid input: {}", a);
38                if a == (0.) {
39                    -$typ::from_bits(1)
40                } else if a < 0. {
41                    $typ::from_bits(a.to_bits() + 1)
42                } else {
43                    $typ::from_bits(a.to_bits() - 1)
44                }
45            }
46
47            #[must_use]
48            // Returns the unit in last place using the definition by John Harrison.
49            // This is the distance between `a` and the next closest float. Note that
50            // `ulp(1) = $typ::EPSILON/2`.
51            fn ulp(a: $typ) -> $typ {
52                debug_assert!(a.is_finite() && a > $typ::MIN, "`ulp` invalid input: {}", a);
53                a.abs() - next_down(a.abs())
54            }
55
56            #[derive(Copy, Clone, Debug)]
57            pub(crate) struct $wrapper($typ);
58
59            impl From<$typ> for $wrapper {
60                fn from(x: $typ) -> Self {
61                    $wrapper(x)
62                }
63            }
64            impl From<$wrapper> for $typ {
65                fn from(x: $wrapper) -> Self {
66                    x.0
67                }
68            }
69
70            #[derive(Clone, Copy, Debug)]
71            pub(crate) struct FloatUniform {
72                low: $typ,
73                high: $typ,
74                intervals: IntervalCollection,
75                inclusive: bool,
76            }
77
78            impl UniformSampler for FloatUniform {
79
80                type X = $wrapper;
81
82                fn new<B1, B2>(low: B1, high: B2) -> Self
83                where
84                    B1: SampleBorrow<Self::X> + Sized,
85                    B2: SampleBorrow<Self::X> + Sized,
86                {
87                    let low = low.borrow().0;
88                    let high = high.borrow().0;
89                    FloatUniform {
90                        low,
91                        high,
92                        intervals: split_interval([low, high]),
93                        inclusive: false,
94                    }
95                }
96
97                fn new_inclusive<B1, B2>(low: B1, high: B2) -> Self
98                where
99                    B1: SampleBorrow<Self::X> + Sized,
100                    B2: SampleBorrow<Self::X> + Sized,
101                {
102                    let low = low.borrow().0;
103                    let high = high.borrow().0;
104
105                    FloatUniform {
106                        low,
107                        high,
108                        intervals: split_interval([low, high]),
109                        inclusive: true,
110                    }
111                }
112
113                fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
114                    let mut intervals = self.intervals;
115                    while intervals.count > 1 {
116                        let new_interval = intervals.get(rng.gen_range(0..intervals.count));
117                        intervals = split_interval(new_interval);
118                    }
119                    let last = intervals.get(0);
120                    let result = *last.choose(rng).expect("Slice is not empty");
121
122                    // These results could happen because the first split might
123                    // overshoot one of the bounds. We could resample in this
124                    // case but for testing data this is not a problem.
125                    let clamped_result = if result < self.low {
126                        debug_assert!(self.low - result < self.intervals.step);
127                        self.low
128                    } else if result > self.high{
129                        debug_assert!(result - self.high < self.intervals.step);
130                        self.high
131                    } else {
132                        result
133                    };
134
135                    if !self.inclusive && clamped_result == self.high  {
136                        return $wrapper(next_down(self.high));
137                    };
138
139                    $wrapper(clamped_result)
140                }
141            }
142
143            impl SampleUniform for $wrapper {
144                type Sampler = FloatUniform;
145            }
146
147            // Divides the range [low, high] into intervals of size epsilon * max(abs(low, high));
148            // Note that the one interval may extend out of the range.
149            #[derive(Clone, Copy, Debug)]
150            struct IntervalCollection {
151                start: $typ,
152                step: $typ,
153                count: $int_typ,
154            }
155
156            fn split_interval([low, high]: [$typ; 2]) -> IntervalCollection {
157                    assert!(low.is_finite(), "low finite");
158                    assert!(high.is_finite(), "high finite");
159                    assert!(high - low > 0., "invalid range");
160
161                    let min_abs = $typ::min(low.abs(), high.abs());
162                    let max_abs = $typ::max(low.abs(), high.abs());
163
164                    let gap = ulp(max_abs);
165
166                    let (start, step) = if low.abs() < high.abs() {
167                        (high, -gap)
168                    } else {
169                        (low, gap)
170                    };
171
172                    let min_gaps = min_abs / gap;
173                    let max_gaps = max_abs / gap;
174                    debug_assert!(
175                        max_gaps.floor() == max_gaps,
176                        "max_gaps is an integer"
177                    );
178
179                    let count = if low.signum() == high.signum() {
180                        max_gaps as $int_typ - min_gaps.floor() as $int_typ
181                    } else {
182                        // `step` is a power of two so `min_gaps` won't be rounded
183                        // except possibly to 0.
184                        if min_gaps == 0. && min_abs > 0. {
185                            max_gaps as $int_typ + 1
186                        } else {
187                            max_gaps as $int_typ + min_gaps.ceil() as $int_typ
188                        }
189                    };
190
191                    debug_assert!(count - 1 <= 2 * MAX_PRECISE_INT);
192
193                    IntervalCollection {
194                        start,
195                        step,
196                        count,
197                    }
198            }
199
200
201            impl IntervalCollection {
202                fn get(&self, index: $int_typ) -> [$typ; 2] {
203                    assert!(index < self.count, "index out of bounds");
204
205                    // `index` might be greater that `MAX_PERCISE_INT`
206                    // which means `MAX_PRECIST_INT as $typ` would round
207                    // to a different number. Fortunately, `index` will
208                    // never be larger than `2 * MAX_PRECISE_INT` (as
209                    // asserted above).
210                    let x = ((index / 2) as $typ).mul_add(
211                        2. * self.step,
212                        (index % 2) as $typ * self.step + self.start,
213                    );
214
215                    let y = x + self.step;
216
217                    if self.step > 0. {
218                        [x, y]
219                    } else {
220                        [y, x]
221                    }
222                }
223            }
224
225
226            // Values greater than MAX_PRECISE_INT may be rounded when converted to float.
227            const MAX_PRECISE_INT: $int_typ =
228                (2 as $int_typ).pow($typ::MANTISSA_DIGITS);
229
230            #[cfg(test)]
231            mod test {
232
233                use super::*;
234                use crate::prelude::*;
235
236                fn sort((left, right): ($typ, $typ)) -> ($typ, $typ) {
237                    if left < right {
238                        (left, right)
239                    } else {
240                        (right, left)
241                    }
242                }
243
244                fn finite() -> impl Strategy<Value = $typ> {
245                    prop::num::$typ::NEGATIVE
246                    | prop::num::$typ::POSITIVE
247                    | prop::num::$typ::NORMAL
248                    | prop::num::$typ::SUBNORMAL
249                    | prop::num::$typ::ZERO
250                }
251
252                fn bounds() -> impl Strategy<Value = ($typ, $typ)> {
253                    (finite(), finite())
254                        .prop_filter("Bounds can't be equal", |(a, b)| a != b)
255                        .prop_map(sort)
256                }
257
258                #[test]
259                fn range_test() {
260                    use crate::test_runner::{RngAlgorithm, TestRng};
261
262                    let mut test_rng = TestRng::deterministic_rng(RngAlgorithm::default());
263                    let (low, high) = (-1., 10.);
264                    let uniform = FloatUniform::new($wrapper(low), $wrapper(high));
265
266                    let samples = (0..100)
267                        .map(|_| $typ::from(uniform.sample(&mut test_rng)));
268                    for s in samples {
269                        assert!(low <= s && s < high);
270                    }
271                }
272
273                #[test]
274                fn range_end_bound_test() {
275                    use crate::test_runner::{RngAlgorithm, TestRng};
276
277                    let mut test_rng = TestRng::deterministic_rng(RngAlgorithm::default());
278                    let (low, high) = (1., 1. + $typ::EPSILON);
279                    let uniform = FloatUniform::new($wrapper(low), $wrapper(high));
280
281                    let mut samples = (0..100)
282                        .map(|_| $typ::from(uniform.sample(&mut test_rng)));
283                    assert!(samples.all(|x| x == 1.));
284                }
285
286                #[test]
287                fn inclusive_range_test() {
288                    use crate::test_runner::{RngAlgorithm, TestRng};
289
290                    let mut test_rng = TestRng::deterministic_rng(RngAlgorithm::default());
291                    let (low, high) = (-1., 10.);
292                    let uniform = FloatUniform::new_inclusive($wrapper(low), $wrapper(high));
293
294                    let samples = (0..100)
295                        .map(|_| $typ::from(uniform.sample(&mut test_rng)));
296                    for s in samples {
297                        assert!(low <= s && s <= high);
298                    }
299                }
300
301                #[test]
302                fn inclusive_range_end_bound_test() {
303                    use crate::test_runner::{RngAlgorithm, TestRng};
304
305                    let mut test_rng = TestRng::deterministic_rng(RngAlgorithm::default());
306                    let (low, high) = (1., 1. + $typ::EPSILON);
307                    let uniform = FloatUniform::new_inclusive($wrapper(low), $wrapper(high));
308
309                    let mut samples = (0..100)
310                        .map(|_| $typ::from(uniform.sample(&mut test_rng)));
311                    assert!(samples.any(|x| x == 1. + $typ::EPSILON));
312                }
313
314                #[test]
315                fn all_floats_in_range_are_possible_1() {
316                    use crate::test_runner::{RngAlgorithm, TestRng};
317
318                    let mut test_rng = TestRng::deterministic_rng(RngAlgorithm::default());
319                    let (low, high) = (1. - $typ::EPSILON, 1. + $typ::EPSILON);
320                    let uniform = FloatUniform::new_inclusive($wrapper(low), $wrapper(high));
321
322                    let mut samples = (0..100)
323                        .map(|_| $typ::from(uniform.sample(&mut test_rng)));
324                    assert!(samples.any(|x| x == 1. - $typ::EPSILON / 2.));
325                }
326
327                #[test]
328                fn all_floats_in_range_are_possible_2() {
329                    use crate::test_runner::{RngAlgorithm, TestRng};
330
331                    let mut test_rng = TestRng::deterministic_rng(RngAlgorithm::default());
332                    let (low, high) = (0., MAX_PRECISE_INT as $typ);
333                    let uniform = FloatUniform::new_inclusive($wrapper(low), $wrapper(high));
334
335                    let mut samples = (0..100)
336                        .map(|_| $typ::from(uniform.sample(&mut test_rng)))
337                        .map(|x| x.fract());
338
339                    assert!(samples.any(|x| x != 0.));
340                }
341
342                #[test]
343                fn max_precise_int_plus_one_is_rounded_down() {
344                    assert_eq!(((MAX_PRECISE_INT + 1) as $typ) as $int_typ, MAX_PRECISE_INT);
345                }
346
347                proptest! {
348                    #[test]
349                    fn next_down_less_than_float(val in finite()) {
350                        prop_assume!(val > $typ::MIN);
351                        prop_assert!(next_down(val) <  val);
352                    }
353
354                    #[test]
355                    fn no_value_between_float_and_next_down(val in finite()) {
356                        prop_assume!(val > $typ::MIN);
357                        let prev = next_down(val);
358                        let avg = prev / 2. + val / 2.;
359                        prop_assert!(avg == prev || avg == val);
360                    }
361
362                    #[test]
363                    fn values_less_than_or_equal_to_max_precise_int_are_not_rounded(i in 0..=MAX_PRECISE_INT) {
364                        prop_assert_eq!((i as $typ) as $int_typ, i);
365                    }
366
367                    #[test]
368                    fn indivisible_intervals_are_split_to_self(val in finite()) {
369                        prop_assume!(val > $typ::MIN);
370                        let prev = next_down(val);
371                        let intervals = split_interval([prev, val]);
372                        prop_assert_eq!(intervals.count, 1);
373                    }
374
375                    #[test]
376                    fn split_intervals_are_the_same_size(
377                            (low, high) in bounds(),
378                            indices: [prop::sample::Index; 32]) {
379
380                        let intervals = split_interval([low, high]);
381
382                        let size = (intervals.count - 1) as usize;
383                        prop_assume!(size > 0);
384
385                        let mut it = indices.iter()
386                            .map(|i| i.index(size) as $int_typ)
387                            .map(|i| intervals.get(i))
388                            .map(|[low, high]| high - low);
389
390                        let interval_size = it.next().unwrap();
391                        let all_equal = it.all(|g| g == interval_size);
392                        prop_assert!(all_equal);
393                    }
394
395                    #[test]
396                    fn split_intervals_are_consecutive(
397                        (low, high) in bounds(),
398                        indices: [prop::sample::Index; 32]) {
399
400                        let intervals = split_interval([low, high]);
401
402                        let size = (intervals.count - 1) as usize;
403                        prop_assume!(size > 1);
404
405                        let mut it = indices.iter()
406                            .map(|i| i.index(size - 1) as $int_typ)
407                            .map(|i| (intervals.get(i), intervals.get(i + 1)));
408
409                        let ascending = it.all(|([_, h1], [l2, _])| h1 == l2);
410                        let descending = it.all(|([l1, _], [_, h2])| l1 == h2);
411
412                        prop_assert!(ascending || descending);
413                    }
414
415                    #[test]
416                    fn first_split_might_slightly_overshoot_one_bound((low, high) in bounds()) {
417                        let intervals = split_interval([low, high]);
418                        let start = intervals.get(0);
419                        let end = intervals.get(intervals.count - 1);
420                        let (low_interval, high_interval) = if  start[0] < end[0] {
421                            (start, end)
422                        } else {
423                            (end, start)
424                        };
425
426                        prop_assert!(
427                            low == low_interval[0] && high_interval[0] < high && high <= high_interval[1] ||
428                            low_interval[0] <= low && low < low_interval[1] && high == high_interval[1]);
429                    }
430
431                    #[test]
432                    fn subsequent_splits_always_match_bounds(
433                        (low, high) in bounds(),
434                        index: prop::sample::Index) {
435                        // This property is true because the distances of split intervals of
436                        // are powers of two so the smaller one always divides the larger.
437
438                        let intervals = split_interval([low, high]);
439                        let size = (intervals.count - 1) as usize;
440
441                        let interval = intervals.get(index.index(size) as $int_typ);
442                        let small_intervals = split_interval(interval);
443
444                        let start = small_intervals.get(0);
445                        let end = small_intervals.get(small_intervals.count - 1);
446                        let (low_interval, high_interval) = if  start[0] < end[0] {
447                            (start, end)
448                        } else {
449                            (end, start)
450                        };
451
452                        prop_assert!(
453                            interval[0] == low_interval[0] &&
454                            interval[1] == high_interval[1]);
455                    }
456                }
457            }
458        }
459    };
460}
461
462float_sampler!(f32, u32, F32U);
463float_sampler!(f64, u64, F64U);