Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
numeric_util.h
1#ifndef SOURCEMETA_CORE_NUMERIC_UTIL_H_
2#define SOURCEMETA_CORE_NUMERIC_UTIL_H_
3
4#include <sourcemeta/core/numeric_decimal.h>
5
6#include <bit> // std::bit_cast
7#include <cassert> // assert
8#include <cmath> // std::modf, std::floor, std::isfinite
9#include <concepts> // std::floating_point, std::integral, std::same_as
10#include <cstdint> // std::uint8_t, std::int64_t, std::uint64_t, std::uint32_t
11#include <limits> // std::numeric_limits
12#include <type_traits> // std::conditional_t
13#include <utility> // std::cmp_greater_equal, std::cmp_less_equal
14
15namespace sourcemeta::core {
16
19template <typename T> auto to_decimal(const T &value) -> Decimal {
20 if constexpr (std::same_as<T, Decimal>) {
21 return value;
22 } else {
23 return Decimal{value};
24 }
25}
26
29template <typename T>
30concept decimal_or_integral = std::same_as<T, Decimal> || std::integral<T>;
31
34template <typename... Ts>
35concept any_decimal = (std::same_as<Ts, Decimal> || ...);
36
51inline constexpr auto is_digit(const char character) -> bool {
52 return character >= '0' && character <= '9';
53}
54
69inline constexpr auto is_positive_digit(const char character) -> bool {
70 return character >= '1' && character <= '9';
71}
72
75template <typename T> constexpr auto is_byte(const T &value) -> bool {
76 if constexpr (std::same_as<T, Decimal>) {
77 return value.is_finite() && value.is_integral() && value >= Decimal{0} &&
78 value <= Decimal{255};
79 } else {
80 return value >= 0 && value <= std::numeric_limits<std::uint8_t>::max();
81 }
82}
83
87template <typename Dividend, typename Divisor>
88 requires decimal_or_integral<Dividend> && decimal_or_integral<Divisor>
89auto divide_floor(const Dividend &dividend, const Divisor &divisor) {
90 if constexpr (any_decimal<Dividend, Divisor>) {
91 Decimal decimal_dividend{to_decimal(dividend)};
92 const Decimal decimal_divisor{to_decimal(divisor)};
93 assert(decimal_dividend.is_integral());
94 assert(decimal_divisor.is_integral());
95 assert(decimal_divisor > Decimal{0});
96 if (decimal_divisor == Decimal{1}) {
97 return decimal_dividend;
98 } else if (decimal_dividend >= Decimal{0}) {
99 return decimal_dividend.divide_integer(decimal_divisor);
100 } else {
101 const Decimal absolute_dividend{
102 decimal_dividend.is_signed() ? -decimal_dividend : decimal_dividend};
103 const Decimal quotient{absolute_dividend.divide_integer(decimal_divisor)};
104 if (absolute_dividend % decimal_divisor == Decimal{0}) {
105 return -quotient;
106 } else {
107 return -(quotient + Decimal{1});
108 }
109 }
110 } else {
111 const auto signed_dividend{static_cast<std::int64_t>(dividend)};
112 const auto unsigned_divisor{static_cast<std::uint64_t>(divisor)};
113 assert(unsigned_divisor > 0);
114 if (unsigned_divisor == 1) {
115 return signed_dividend;
116 } else if (signed_dividend >= 0) {
117 return static_cast<std::int64_t>(
118 static_cast<std::uint64_t>(signed_dividend) / unsigned_divisor);
119 } else {
120 // Negate in unsigned to avoid UB for INT64_MIN
121 const std::uint64_t absolute_dividend{
122 static_cast<std::uint64_t>(0) -
123 static_cast<std::uint64_t>(signed_dividend)};
124 return -(static_cast<std::int64_t>(
125 1 + ((absolute_dividend - 1) / unsigned_divisor)));
126 }
127 }
128}
129
133template <typename Dividend, typename Divisor>
134 requires decimal_or_integral<Dividend> && decimal_or_integral<Divisor>
135auto divide_ceil(const Dividend &dividend, const Divisor &divisor) {
136 if constexpr (any_decimal<Dividend, Divisor>) {
137 Decimal decimal_dividend{to_decimal(dividend)};
138 const Decimal decimal_divisor{to_decimal(divisor)};
139 assert(decimal_dividend.is_integral());
140 assert(decimal_divisor.is_integral());
141 assert(decimal_divisor > Decimal{0});
142 if (decimal_divisor == Decimal{1}) {
143 return decimal_dividend;
144 } else if (decimal_dividend >= Decimal{0}) {
145 const Decimal quotient{decimal_dividend.divide_integer(decimal_divisor)};
146 if (decimal_dividend % decimal_divisor == Decimal{0}) {
147 return quotient;
148 } else {
149 return quotient + Decimal{1};
150 }
151 } else {
152 const Decimal absolute_dividend{
153 decimal_dividend.is_signed() ? -decimal_dividend : decimal_dividend};
154 return -(absolute_dividend.divide_integer(decimal_divisor));
155 }
156 } else {
157 const auto signed_dividend{static_cast<std::int64_t>(dividend)};
158 const auto unsigned_divisor{static_cast<std::uint64_t>(divisor)};
159 assert(unsigned_divisor > 0);
160 if (unsigned_divisor == 1) {
161 return signed_dividend;
162 } else if (signed_dividend >= 0) {
163 if (static_cast<std::uint64_t>(signed_dividend) + unsigned_divisor <
164 unsigned_divisor) {
165 return static_cast<std::int64_t>(
166 (static_cast<std::uint64_t>(signed_dividend) / unsigned_divisor) +
167 1 - (1 / unsigned_divisor));
168 } else {
169 return static_cast<std::int64_t>(
170 (static_cast<std::uint64_t>(signed_dividend) + unsigned_divisor -
171 1) /
172 unsigned_divisor);
173 }
174 } else {
175 // Negate in unsigned to avoid UB for INT64_MIN
176 return -(static_cast<std::int64_t>(
177 (static_cast<std::uint64_t>(0) -
178 static_cast<std::uint64_t>(signed_dividend)) /
179 unsigned_divisor));
180 }
181 }
182}
183
188template <typename Minimum, typename Maximum, typename Multiplier>
189 requires decimal_or_integral<Minimum> && decimal_or_integral<Maximum> &&
190 decimal_or_integral<Multiplier>
191auto count_multiples(const Minimum &minimum, const Maximum &maximum,
192 const Multiplier &multiplier) {
194 const Decimal decimal_minimum{to_decimal(minimum)};
195 const Decimal decimal_maximum{to_decimal(maximum)};
196 const Decimal decimal_multiplier{to_decimal(multiplier)};
197 assert(decimal_minimum.is_integral());
198 assert(decimal_maximum.is_integral());
199 assert(decimal_multiplier.is_integral());
200 assert(decimal_minimum <= decimal_maximum);
201 assert(decimal_multiplier > Decimal{0});
202 return divide_floor(decimal_maximum, decimal_multiplier) -
203 divide_floor(decimal_minimum - Decimal{1}, decimal_multiplier);
204 } else {
205 const auto signed_minimum{static_cast<std::int64_t>(minimum)};
206 const auto signed_maximum{static_cast<std::int64_t>(maximum)};
207 const auto signed_multiplier{static_cast<std::int64_t>(multiplier)};
208 assert(signed_minimum <= signed_maximum);
209 assert(signed_multiplier > 0);
210 const auto unsigned_multiplier{
211 static_cast<std::uint64_t>(signed_multiplier)};
212 const auto multiples_to_maximum{
213 divide_floor(signed_maximum, unsigned_multiplier)};
214 const auto multiples_below_minimum{
215 divide_floor(signed_minimum, unsigned_multiplier)};
216 // Count the multiples up to the maximum and subtract those strictly below
217 // the minimum. The lower bound is derived without forming one less than the
218 // smallest value, which would overflow for the most negative input, and the
219 // subtraction is performed in unsigned arithmetic so the difference cannot
220 // overflow a signed integer
221 const std::uint64_t minimum_is_multiple{
222 signed_minimum % signed_multiplier == 0 ? 1U : 0U};
223 return static_cast<std::uint64_t>(multiples_to_maximum) -
224 static_cast<std::uint64_t>(multiples_below_minimum) +
225 minimum_is_multiple;
226 }
227}
228
231template <unsigned int T>
232constexpr auto uint_max = []() -> std::uint64_t {
233 static_assert(T > 0 && T < 64, "uint_max<T> requires 0 < T < 64");
234 return (std::uint64_t{1} << T) - 1;
235}();
236
240template <typename T>
241constexpr auto is_within(const T &value, const std::int64_t lower,
242 const std::int64_t higher) noexcept -> bool {
243 // Compare across signedness without converting an unsigned value against a
244 // negative bound, which would otherwise wrap the bound to a large positive
245 return std::cmp_greater_equal(value, lower) &&
246 std::cmp_less_equal(value, higher);
247}
248
252template <typename T>
253constexpr auto is_within(const T &value, const std::uint64_t lower,
254 const std::uint64_t higher) noexcept -> bool {
255 if (value >= 0) {
256 return static_cast<std::uint64_t>(value) >= lower &&
257 static_cast<std::uint64_t>(value) <= higher;
258 } else {
259 return false;
260 }
261}
262
266inline auto is_within(const Decimal &value, const Decimal &lower,
267 const Decimal &higher) -> bool {
268 return value >= lower && value <= higher;
269}
270
273template <typename T> auto abs(const T &value) {
274 if constexpr (std::same_as<T, Decimal>) {
275 return value.is_signed() ? -value : value;
276 } else {
277 if (value < 0) {
278 // Negate in unsigned to avoid UB for INT64_MIN
279 return static_cast<std::uint64_t>(0) - static_cast<std::uint64_t>(value);
280 } else {
281 return static_cast<std::uint64_t>(value);
282 }
283 }
284}
285
290constexpr auto closest_smallest_exponent(const std::uint64_t value,
291 const std::uint8_t base,
292 const std::uint8_t exponent_start,
293 const std::uint8_t exponent_end)
294 -> std::uint8_t {
295 assert(exponent_start <= exponent_end);
296 std::uint64_t result{base};
297 for (std::uint8_t exponent{1}; exponent < exponent_end; exponent++) {
298 // Test whether the next power exceeds the value without forming it, since
299 // result multiplied by base could wrap the accumulator
300 const bool next_power_exceeds_value{result > value / base};
301 if (next_power_exceeds_value) {
302 if (exponent >= exponent_start) {
303 return exponent;
304 }
305
306 continue;
307 }
308
309 result *= base;
310 }
311
312 assert(result <= value);
313 return exponent_end;
314}
315
319template <std::floating_point Real>
320constexpr auto correct_ieee754(const Real value) -> Real {
321 assert(std::isfinite(value));
322 const Real threshold{static_cast<Real>(0.000000001)};
323 const Real base{std::floor(value)};
324 const Real next{base + 1};
325 if (next - value <= threshold) {
326 return next;
327 } else if (value - base <= threshold) {
328 return base;
329 } else {
330 return value;
331 }
332}
333
338template <std::integral Integer, std::floating_point Real>
339constexpr auto real_digits(Real value, std::uint64_t &point_position)
340 -> Integer {
341 assert(std::isfinite(value));
342 Real integral_part;
343 std::uint64_t shifts{0};
344
345 Real fractional_part{std::modf(value, &integral_part)};
346 while (fractional_part != 0.0) {
347 value *= 10;
348 shifts += 1;
349 fractional_part = std::modf(correct_ieee754(value), &integral_part);
350 }
351
352 point_position = shifts;
353 return static_cast<Integer>(std::floor(integral_part));
354}
355
370template <std::floating_point Real>
371 requires(sizeof(Real) == sizeof(std::uint32_t) ||
372 sizeof(Real) == sizeof(std::uint64_t))
373auto real_equal(const Real left, const Real right) -> bool {
374 using Bits = std::conditional_t<sizeof(Real) == sizeof(std::uint32_t),
375 std::uint32_t, std::uint64_t>;
376
377 // A NaN equals nothing and an infinity equals only the same infinity, both
378 // handled exactly here. Only finite values fall through to the tolerance
379 // below, which would otherwise treat the largest finite value and infinity as
380 // equal because their encodings are adjacent
381 if (!std::isfinite(left) || !std::isfinite(right)) {
382 return left == right;
383 }
384
385 // Map the sign-and-magnitude bit pattern to a biased ordering in which
386 // adjacent representable values differ by one, so their distance counts the
387 // units in the last place between them
388 constexpr Bits sign_bit{Bits{1} << (8 * sizeof(Bits) - 1)};
389 const Bits left_bits{std::bit_cast<Bits>(left)};
390 const Bits right_bits{std::bit_cast<Bits>(right)};
391 const Bits left_biased{(sign_bit & left_bits) != 0
392 ? static_cast<Bits>(~left_bits + Bits{1})
393 : static_cast<Bits>(sign_bit | left_bits)};
394 const Bits right_biased{(sign_bit & right_bits) != 0
395 ? static_cast<Bits>(~right_bits + Bits{1})
396 : static_cast<Bits>(sign_bit | right_bits)};
397 constexpr Bits maximum_units_in_last_place{4};
398 return (left_biased >= right_biased
399 ? left_biased - right_biased
400 : right_biased - left_biased) <= maximum_units_in_last_place;
401}
402
403} // namespace sourcemeta::core
404
405#endif
Definition numeric_util.h:35
Definition numeric_util.h:30
auto divide_integer(const Decimal &other) const -> Decimal
Integer division (truncate toward zero).
SOURCEMETA_FORCEINLINE auto is_signed() const -> bool
Check if the decimal number is signed (negative, including -0).
Definition numeric_decimal.h:197
auto is_integral() const -> bool
Definition numeric_decimal.h:21
auto to_decimal(const T &value) -> Decimal
Definition numeric_util.h:19
auto divide_floor(const Dividend &dividend, const Divisor &divisor)
Definition numeric_util.h:89
auto divide_ceil(const Dividend &dividend, const Divisor &divisor)
Definition numeric_util.h:135
auto real_equal(const Real left, const Real right) -> bool
Definition numeric_util.h:373
constexpr auto correct_ieee754(const Real value) -> Real
Definition numeric_util.h:320
constexpr auto is_byte(const T &value) -> bool
Definition numeric_util.h:75
constexpr auto is_digit(const char character) -> bool
Definition numeric_util.h:51
constexpr auto closest_smallest_exponent(const std::uint64_t value, const std::uint8_t base, const std::uint8_t exponent_start, const std::uint8_t exponent_end) -> std::uint8_t
Definition numeric_util.h:290
constexpr auto is_positive_digit(const char character) -> bool
Definition numeric_util.h:69
auto count_multiples(const Minimum &minimum, const Maximum &maximum, const Multiplier &multiplier)
Definition numeric_util.h:191
auto abs(const T &value)
Definition numeric_util.h:273
constexpr auto uint_max
Definition numeric_util.h:232
constexpr auto is_within(const T &value, const std::int64_t lower, const std::int64_t higher) noexcept -> bool
Definition numeric_util.h:241
constexpr auto real_digits(Real value, std::uint64_t &point_position) -> Integer
Definition numeric_util.h:339