ChimeraTK-DeviceAccess 03.26.00
Loading...
Searching...
No Matches
testNumericConverter.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include <cmath>
5#define BOOST_TEST_DYN_LINK
6#define BOOST_TEST_MODULE TestNumericConverter
7#include <boost/test/unit_test.hpp>
8using namespace boost::unit_test_framework;
9
10#include <NumericConverter.h>
11
12using namespace ChimeraTK::numeric;
13
14/**********************************************************************************************************************/
15
16// List of types to test with. ChimeraTK::Void is tested separately.
17using IntTypes =
18 std::tuple<uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, bool, ChimeraTK::Boolean>;
19using FloatTypes = std::tuple<float, double>;
20
21/**********************************************************************************************************************/
22
23// Helper function to iterate over the types of an std::tuple
24template<typename Tuple, typename F>
25void forEachType(F&& f) {
26 []<std::size_t... Is>(F&& g, std::index_sequence<Is...>) {
27 (g.template operator()<std::tuple_element_t<Is, Tuple>>(), ...);
28 }(std::forward<F>(f), std::make_index_sequence<std::tuple_size_v<Tuple>>{});
29};
30
31/**********************************************************************************************************************/
32
33// Note: This test does almost nothing at runtime, almost all checks are implemented as static_asserts at compile time.
34BOOST_AUTO_TEST_CASE(TestNumericConverter) {
35 // Conversion from int to float
36 forEachType<IntTypes>([]<typename I>() {
37 forEachType<FloatTypes>([]<typename F>() {
38 // Note: we might not always distinguish "std::numeric_limits<I>::max()" from "std::numeric_limits<I>::max() - 1"
39 // etc. in the floating point type, but the same imprecision must happen here on both sides of the ==.
40 static_assert(convert<F>(std::numeric_limits<I>::max()) == F(std::numeric_limits<I>::max()));
41 static_assert(convert<F>(std::numeric_limits<I>::lowest()) == F(std::numeric_limits<I>::lowest()));
42 static_assert(convert<F>(std::numeric_limits<I>::max() - 1) == F(std::numeric_limits<I>::max() - 1));
43 static_assert(convert<F>(std::numeric_limits<I>::lowest() + 1) == F(std::numeric_limits<I>::lowest() + 1));
44
45 static_assert(convert<F>(I(1)) == F(1));
46 static_assert(convert<F>(I(0)) == F(0));
47 if constexpr(!ChimeraTK::isBoolean<I>) {
48 static_assert(convert<F>(I(42)) == F(42));
49 }
50
51 if constexpr(std::is_signed_v<I>) {
52 static_assert(convert<F>(I(-1)) == F(-1));
53 static_assert(convert<F>(I(-120)) == F(-120));
54 }
55 });
56 });
57
58 // Conversion from float to int
59 forEachType<IntTypes>([]<typename I>() {
60 forEachType<FloatTypes>([]<typename F>() {
61 if constexpr(!std::is_same_v<I, int64_t> && !std::is_same_v<I, uint64_t> &&
62 (std::is_same_v<F, double> || (!std::is_same_v<I, int32_t> && !std::is_same_v<I, uint32_t>))) {
63 // these checks work only if lowest() and max() of the I type can be exactly represented in the F type
64 static_assert(convert<I>(F(std::numeric_limits<I>::max())) == std::numeric_limits<I>::max());
65 static_assert(convert<I>(F(std::numeric_limits<I>::max() - 1)) == std::numeric_limits<I>::max() - 1);
66
67 static_assert(convert<I>(F(std::numeric_limits<I>::lowest())) == std::numeric_limits<I>::lowest());
68 static_assert(convert<I>(F(std::numeric_limits<I>::lowest() + 1)) == std::numeric_limits<I>::lowest() + 1);
69
70 // check proper rounding for big values
71 static_assert(convert<I>(F(std::numeric_limits<I>::max()) - F(0.51)) == std::numeric_limits<I>::max() - 1);
72 static_assert(convert<I>(F(std::numeric_limits<I>::max()) - F(0.49)) == std::numeric_limits<I>::max());
73
74 static_assert(
75 convert<I>(F(std::numeric_limits<I>::lowest()) + F(0.51)) == std::numeric_limits<I>::lowest() + 1);
76 static_assert(convert<I>(F(std::numeric_limits<I>::lowest()) + F(0.49)) == std::numeric_limits<I>::lowest());
77 }
78 // the next two checks might be insensitive for some types due to limited floating point precision
79 static_assert(convert<I>(F(std::numeric_limits<I>::max()) + F(0.49)) == std::numeric_limits<I>::max());
80 static_assert(convert<I>(F(std::numeric_limits<I>::max()) + F(0.51)) == std::numeric_limits<I>::max());
81 // make sure the next one does become insensitive due to limited precision
82 static_assert(F(std::numeric_limits<I>::max()) + F(1.E13) != F(std::numeric_limits<I>::max()));
83 static_assert(convert<I>(F(std::numeric_limits<I>::max()) + F(1.E13)) == std::numeric_limits<I>::max());
84
85 static_assert(convert<I>(F(std::numeric_limits<I>::lowest()) - F(0.49)) == std::numeric_limits<I>::lowest());
86 if constexpr(!ChimeraTK::isBoolean<I>) {
87 static_assert(convert<I>(F(std::numeric_limits<I>::lowest()) - F(0.51)) == std::numeric_limits<I>::lowest());
88 static_assert(convert<I>(F(std::numeric_limits<I>::lowest()) - F(100000.)) == std::numeric_limits<I>::lowest());
89 }
90 else {
91 static_assert(convert<I>(-F(0.51)) == true);
92 static_assert(convert<I>(-F(100000.)) == true);
93 }
94
95 static_assert(convert<I>(F(1)) == I(1));
96 static_assert(convert<I>(F(0)) == I(0));
97 static_assert(convert<I>(F(42)) == I(42));
98 if constexpr(std::is_signed_v<I>) {
99 static_assert(convert<I>(F(-1)) == I(-1));
100 static_assert(convert<I>(F(-120)) == I(-120));
101 }
102 else {
103 if constexpr(!ChimeraTK::isBoolean<I>) {
104 static_assert(convert<I>(F(-1)) == 0);
105 static_assert(convert<I>(F(-120)) == 0);
106 }
107 else {
108 static_assert(convert<I>(F(-1)) == true);
109 static_assert(convert<I>(F(-120)) == true);
110 }
111 }
112
113 // check proper rounding
114 static_assert(convert<I>(F(0.49999)) == 0);
115 static_assert(convert<I>(F(0.50001)) == 1);
116 static_assert(convert<I>(F(1.49999)) == 1);
117 if constexpr(!ChimeraTK::isBoolean<I>) {
118 static_assert(convert<I>(F(1.50001)) == 2);
119 }
120 static_assert(convert<I>(F(-0.49999)) == 0);
121 if constexpr(std::is_signed_v<I>) {
122 static_assert(convert<I>(F(-0.50001)) == -1);
123 static_assert(convert<I>(F(-1.49999)) == -1);
124 static_assert(convert<I>(F(-1.50001)) == -2);
125 }
126 else {
127 if constexpr(!ChimeraTK::isBoolean<I>) {
128 static_assert(convert<I>(F(-0.50001)) == 0);
129 static_assert(convert<I>(F(-1.49999)) == 0);
130 static_assert(convert<I>(F(-1.50001)) == 0);
131 }
132 else {
133 static_assert(convert<I>(F(-0.50001)) == true);
134 static_assert(convert<I>(F(-1.49999)) == true);
135 static_assert(convert<I>(F(-1.50001)) == true);
136 }
137 }
138
139 // check Inf and NaN
140 static_assert(convert<I>(std::numeric_limits<F>::infinity()) == std::numeric_limits<I>::max());
141 if constexpr(!ChimeraTK::isBoolean<I>) {
142 static_assert(convert<I>(-std::numeric_limits<F>::infinity()) == std::numeric_limits<I>::lowest());
143 }
144 else {
145 static_assert(convert<I>(-std::numeric_limits<F>::infinity()) == true);
146 }
147
148 if constexpr(std::is_signed_v<I>) {
149 static_assert(convert<I>(std::numeric_limits<F>::quiet_NaN()) == std::numeric_limits<I>::lowest());
150 }
151 else {
152 if constexpr(!ChimeraTK::isBoolean<I>) {
153 static_assert(convert<I>(std::numeric_limits<F>::quiet_NaN()) == std::numeric_limits<I>::max());
154 }
155 else {
156 static_assert(convert<I>(std::numeric_limits<F>::quiet_NaN()) == false);
157 }
158 }
159 });
160 });
161
162 // Conversion from int to int
163 forEachType<IntTypes>([]<typename I1>() {
164 forEachType<IntTypes>([]<typename I2>() {
165 if constexpr(detail::greaterMaximum<I2, I1>()) {
166 // I2 can represent bigger values than I1
167 static_assert(convert<I2>(std::numeric_limits<I1>::max()) == std::numeric_limits<I1>::max());
168 static_assert(convert<I2>(std::numeric_limits<I1>::max() - 1) == std::numeric_limits<I1>::max() - 1);
169
170 static_assert(convert<I1>(I2(std::numeric_limits<I1>::max()) + 1) == std::numeric_limits<I1>::max());
171 static_assert(convert<I1>(std::numeric_limits<I2>::max()) == std::numeric_limits<I1>::max());
172 }
173
174 static_assert(convert<I1>(I2(1)) == 1);
175 static_assert(convert<I1>(I2(0)) == 0);
176
177 if constexpr(std::is_signed_v<I1> && std::is_signed_v<I2>) {
178 // both are signed
179 if constexpr(std::numeric_limits<I2>::lowest() < std::numeric_limits<I1>::lowest()) {
180 // I2 can represent more negative values than I1
181 static_assert(convert<I2>(std::numeric_limits<I1>::lowest()) == std::numeric_limits<I1>::lowest());
182 static_assert(convert<I2>(std::numeric_limits<I1>::lowest() + 1) == std::numeric_limits<I1>::lowest() + 1);
183
184 static_assert(convert<I1>(I2(std::numeric_limits<I1>::lowest()) - 1) == std::numeric_limits<I1>::lowest());
185 static_assert(convert<I1>(std::numeric_limits<I2>::lowest()) == std::numeric_limits<I1>::lowest());
186 }
187 }
188
189 if constexpr(std::is_signed_v<I1> && !std::is_signed_v<I2>) {
190 // only I1 is signed
191 if constexpr(!ChimeraTK::isBoolean<I2>) {
192 static_assert(convert<I2>(std::numeric_limits<I1>::lowest()) == 0);
193 static_assert(convert<I2>(std::numeric_limits<I1>::lowest() + 1) == 0);
194 static_assert(convert<I2>(-1) == 0);
195 }
196 else {
197 // any non-zero value, including negative values are considered "true"
198 static_assert(convert<I2>(std::numeric_limits<I1>::lowest()) == true);
199 static_assert(convert<I2>(std::numeric_limits<I1>::lowest() + 1) == true);
200 static_assert(convert<I2>(-1) == true);
201 }
202 }
203 });
204 });
205
206 // conversion from float to float
207 forEachType<FloatTypes>([]<typename F1>() {
208 forEachType<FloatTypes>([]<typename F2>() {
209 if constexpr(detail::greaterMaximum<F2, F1>()) {
210 // F2 = double, F1 = float
211 static_assert(convert<F1>(std::numeric_limits<F2>::max()) == std::numeric_limits<F1>::max());
212 static_assert(convert<F1>(std::numeric_limits<F2>::lowest()) == std::numeric_limits<F1>::lowest());
213
214 static_assert(convert<F2>(std::numeric_limits<F1>::max()) == F2(std::numeric_limits<F1>::max()));
215 static_assert(convert<F2>(std::numeric_limits<F1>::lowest()) == F2(std::numeric_limits<F1>::lowest()));
216 }
217
218 static_assert(convert<F2>(F1(0.)) == F2(0.));
219 static_assert(convert<F2>(F1(1.)) == F2(1.));
220 static_assert(convert<F2>(F1(-1.)) == F2(-1.));
221 static_assert(convert<F2>(F1(0.12345)) == F2(F1(0.12345)));
222
223 // check retention of sign bit for zero
224 // (Note: std::signbit isn't yet constexpr with clang on Ubuntu 24)
225 BOOST_TEST(std::signbit(convert<F2>(F1(0.))) == 0); // signbit 0 means "positive"
226 constexpr F2 result = convert<F2>(F1(0.) / F1(-1.)); // "-0." will not give us a negative 0 in C++
227 static_assert(result == 0); // negative and positive zero compare equal in C++
228 BOOST_TEST(std::signbit(result) == 1); // signbit 1 means "negative"
229
230 static_assert(std::isnan(convert<F2>(std::numeric_limits<F1>::quiet_NaN())));
231 static_assert(std::isinf(convert<F2>(std::numeric_limits<F1>::infinity())));
232 static_assert(convert<F2>(std::numeric_limits<F1>::infinity()) == std::numeric_limits<F2>::infinity());
233 static_assert(convert<F2>(-std::numeric_limits<F1>::infinity()) == -std::numeric_limits<F2>::infinity());
234 });
235 });
236
237 // conversion from/to Void
238 forEachType<FloatTypes>([]<typename F>() {
239 static_assert(convert<F>(ChimeraTK::Void{}) == F(0));
240 constexpr auto result1 = convert<ChimeraTK::Void>(F(0.0));
241 static_assert(std::is_same_v<decltype(result1), const ChimeraTK::Void>);
242 constexpr auto result2 = convert<ChimeraTK::Void>(F(123.456));
243 static_assert(std::is_same_v<decltype(result2), const ChimeraTK::Void>);
244 });
245 forEachType<IntTypes>([]<typename I>() {
246 static_assert(convert<I>(ChimeraTK::Void{}) == I(0));
247 constexpr auto result1 = convert<ChimeraTK::Void>(I(0));
248 static_assert(std::is_same_v<decltype(result1), const ChimeraTK::Void>);
249 constexpr auto result2 = convert<ChimeraTK::Void>(I(123));
250 static_assert(std::is_same_v<decltype(result2), const ChimeraTK::Void>);
251 });
252}
253
254/**********************************************************************************************************************/
Wrapper Class for void.
Definition Void.h:15
void forEachType(F &&f)
std::tuple< float, double > FloatTypes
BOOST_AUTO_TEST_CASE(TestNumericConverter)
std::tuple< uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, bool, ChimeraTK::Boolean > IntTypes