Hegel 0.11.4
Property-based testing for C++
Loading...
Searching...
No Matches
numeric.h
1#pragma once
2
3#include <cstdint>
4#include <limits>
5
6#include "hegel/config.h"
7#include "hegel/core.h"
8
9namespace hegel::generators {
10
11 // =============================================================================
12 // Parameter structs
13 // =============================================================================
14
19 template <typename T> struct IntegersParams {
20 std::optional<T>
22 std::optional<T>
24 };
25
30 template <typename T> struct FloatsParams {
31 std::optional<T> min_value;
32 std::optional<T> max_value;
34 false;
36 false;
37 std::optional<bool>
39 std::optional<bool>
41 };
42
44 // Concrete IGenerator<T> subclass produced by integers().
45 template <typename T>
46 HEGEL_REQUIRES(std::is_integral_v<T>)
47 class IntegerGenerator : public IGenerator<T> {
48 static_assert(std::is_integral_v<T>,
49 "integers<T> requires an integral type T");
50
51 public:
52 explicit IntegerGenerator(const IntegersParams<T>& params = {})
53 : min_(params.min_value.value_or(std::numeric_limits<T>::min())),
54 max_(params.max_value.value_or(std::numeric_limits<T>::max())) {
55 if (min_ > max_) {
56 throw std::invalid_argument(
57 "Cannot have max_value < min_value");
58 }
59 }
60
61 T do_draw(const TestCase& tc) const override {
62 if constexpr (std::is_signed_v<T>) {
63 return static_cast<T>(hegel::internal::draw_integer(
64 tc, static_cast<int64_t>(min_),
65 static_cast<int64_t>(max_)));
66 } else {
67 return static_cast<T>(hegel::internal::draw_integer_unsigned(
68 tc, static_cast<uint64_t>(min_),
69 static_cast<uint64_t>(max_)));
70 }
71 }
72
73 private:
74 T min_;
75 T max_;
76 };
77
78 // Concrete IGenerator<T> subclass produced by floats(). Resolves the
79 // engine draw parameters once at construction.
80 template <typename T>
81 HEGEL_REQUIRES(std::is_floating_point_v<T>)
82 class FloatGenerator : public IGenerator<T> {
83 static_assert(std::is_floating_point_v<T>,
84 "floats<T> requires a floating-point type T");
85
86 public:
87 explicit FloatGenerator(const FloatsParams<T>& params = {}) {
88 bool has_min = params.min_value.has_value();
89 bool has_max = params.max_value.has_value();
90 allow_nan_ = params.allow_nan.value_or(!has_min && !has_max);
91 allow_infinity_ =
92 params.allow_infinity.value_or(!has_min || !has_max);
93 if (allow_nan_ && (has_min || has_max)) {
94 throw std::invalid_argument(
95 "Cannot have allow_nan=true with min_value or max_value");
96 }
97 if (has_min && has_max && *params.min_value > *params.max_value) {
98 throw std::invalid_argument(
99 "Cannot have max_value < min_value");
100 }
101 if (allow_infinity_ && has_min && has_max) {
102 throw std::invalid_argument(
103 "Cannot have allow_infinity=true with both min_value and "
104 "max_value");
105 }
106
107 // An unset bound is the widest the draw allows: infinite when
108 // infinities may be drawn, else the type's finite extreme.
109 min_ = has_min ? static_cast<double>(*params.min_value)
110 : allow_infinity_
111 ? -std::numeric_limits<double>::infinity()
112 : static_cast<double>(std::numeric_limits<T>::lowest());
113 max_ = has_max ? static_cast<double>(*params.max_value)
114 : allow_infinity_
115 ? std::numeric_limits<double>::infinity()
116 : static_cast<double>(std::numeric_limits<T>::max());
117 exclude_min_ = params.exclude_min;
118 exclude_max_ = params.exclude_max;
119 }
120
121 T do_draw(const TestCase& tc) const override {
122 constexpr uint32_t width = sizeof(T) * 8;
123 return static_cast<T>(hegel::internal::draw_float(
124 tc, width, min_, max_, allow_nan_, allow_infinity_,
125 exclude_min_, exclude_max_,
126 static_cast<double>(std::numeric_limits<T>::denorm_min())));
127 }
128
129 private:
130 double min_;
131 double max_;
132 bool allow_nan_;
133 bool allow_infinity_;
134 bool exclude_min_;
135 bool exclude_max_;
136 };
138
141
157 template <typename T = int64_t>
158 HEGEL_REQUIRES(std::is_integral_v<T>)
159 Generator<T> integers(IntegersParams<T> params = {}) {
160 return Generator<T>(new IntegerGenerator<T>(std::move(params)));
161 }
162
179 template <typename T = double>
180 HEGEL_REQUIRES(std::is_floating_point_v<T>)
181 Generator<T> floats(FloatsParams<T> params = {}) {
182 return Generator<T>(new FloatGenerator<T>(std::move(params)));
183 }
184
186
187} // namespace hegel::generators
The base class of all generators.
Definition core.h:67
Hegel generators.
Definition core.h:17
Generator< T > integers(IntegersParams< T > params={})
Generate random integers. For a given integral type T, produces values in the range [std::numeric_lim...
Definition numeric.h:159
Generator< T > floats(FloatsParams< T > params={})
Generate random floating point numbers.
Definition numeric.h:181
Parameters for floats() generator.
Definition numeric.h:30
bool exclude_min
If true, exclude min_value (exclusive bound).
Definition numeric.h:33
std::optional< T > min_value
Minimum value. Default: no minimum.
Definition numeric.h:31
std::optional< T > max_value
Maximum value. Default: no maximum.
Definition numeric.h:32
std::optional< bool > allow_nan
Allow NaN. Default: true if unbounded.
Definition numeric.h:38
bool exclude_max
If true, exclude max_value (exclusive bound).
Definition numeric.h:35
std::optional< bool > allow_infinity
Allow infinity. Default: true if unbounded.
Definition numeric.h:40
Base interface for generators.
Definition core.h:29
Parameters for integers() generator.
Definition numeric.h:19
std::optional< T > max_value
Maximum value (inclusive). Default: type maximum.
Definition numeric.h:23
std::optional< T > min_value
Minimum value (inclusive). Default: type minimum.
Definition numeric.h:21