sanisizer
Sanitize sizes to avoid integer overflow
Loading...
Searching...
No Matches
float.hpp
Go to the documentation of this file.
1#ifndef SANISIZER_FLOAT_HPP
2#define SANISIZER_FLOAT_HPP
3
4#include <limits>
5#include <cmath>
6#include <stdexcept>
7#include <type_traits>
8#include <cassert>
9
10#include "utils.hpp"
11#include "generics.hpp"
12
18namespace sanisizer {
19
30template<typename Float_>
32 static_assert(std::is_floating_point<Float_>::value);
33 assert(x >= 0);
34 assert(std::isfinite(x));
35 assert(std::trunc(x) == x);
36
37#ifndef SANISIZER_FLOAT_FORCE_FREXP
38 if constexpr(std::numeric_limits<Float_>::radix == 2) {
39 if (x == 0) {
40 return 0;
41 } else {
42 // ilogb returns an 'exp' such that 2^exp <= x < 2^(exp + 1).
43 return std::ilogb(x) + 1;
44 }
45 } else {
46#endif
47 // Ensure we're covered for weird float types where the radix is not 2.
48 int exp;
49 std::frexp(x, &exp);
50 // frexp guarantees that 2^(exp - 1) <= x < 2^exp.
51 return exp;
52#ifndef SANISIZER_FLOAT_FORCE_FREXP
53 }
54#endif
55}
56
72template<typename Integer_, typename Float_>
73Integer_ from_float(Float_ x) {
74 static_assert(std::is_floating_point<Float_>::value);
75 static_assert(std::is_integral<Integer_>::value);
76
77 if (!std::isfinite(x)) {
78 throw std::range_error("expected a finite value");
79 }
80 if (x < 0) {
81 throw std::range_error("expected a non-negative value");
82 }
83 x = std::trunc(x);
84
85 constexpr auto output_precision = std::numeric_limits<Integer_>::digits;
86 if (required_bits_for_float(x) > output_precision) {
87 throw std::overflow_error("value is too large for the specified integer type");
88 }
89
90 return x;
91}
92
114template<typename Float_, typename Value_>
115Float_ to_float(Value_ x) {
116 const auto val = get_value(x);
117
118 // protect against the various -1 operations.
119 constexpr auto xmax = get_max<I<decltype(x)> >();
120 if constexpr(xmax == 0) {
121 return 0;
122 } else if (val == 0) {
123 return 0;
124 }
125
126 constexpr auto frad = std::numeric_limits<Float_>::radix;
127 constexpr auto fdig = std::numeric_limits<Float_>::digits;
128
129 // Only considering IEEE-754 binary formats, to keep things simple.
130 // Also I don't even have the hardware to test the other formats.
131 static_assert(frad == 2);
132 static_assert(std::numeric_limits<Float_>::is_iec559);
133
134 // Checking digits first, because attempting to right-shift an integer by a number equal to or more than its bits is UB.
135 if constexpr(std::numeric_limits<I<decltype(val)> >::digits > fdig) {
136 // We can represent '2^fdig' itself due to the implicit leading bit on the mantissa in a (normal) IEEE binary float.
137 // Thus, we subtract 1 to avoid throwing an error when 'x == 2^fdig'.
138 if constexpr((xmax - 1) >> fdig) {
139 const auto y = (val - 1) >> fdig;
140 if (y) {
141 throw std::overflow_error("value is too large for the specified floating-point type");
142 }
143 }
144 }
145
146 return val;
147}
148
149}
150
151#endif
Generics for operating on integer representations.
Sanitize sizes to avoid integer overflow.
Definition Bounded.hpp:15
Float_ to_float(Value_ x)
Definition float.hpp:115
constexpr auto get_value(const Value_ x)
Definition generics.hpp:23
Integer_ from_float(Float_ x)
Definition float.hpp:73
int required_bits_for_float(Float_ x)
Definition float.hpp:31
constexpr auto get_max()
Definition generics.hpp:39