ChimeraTK-DeviceAccess 03.26.00
Loading...
Searching...
No Matches
NumericConverter.h
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#pragma once
4
5#include "Boolean.h"
6#include "Void.h"
7
8#include <cmath>
9#include <concepts>
10#include <limits>
11#include <type_traits>
12
14
15 /********************************************************************************************************************/
16
20 template<typename T>
21 concept Integral = std::integral<T> || std::is_same_v<T, ChimeraTK::Boolean>;
22
26 template<typename T>
27 concept Arithmetic = Integral<T> || std::floating_point<T>;
28
32 template<typename T>
33 concept ArithmeticOrVoid = Integral<T> || std::floating_point<T> || std::is_same_v<T, ChimeraTK::Void>;
34
35 /********************************************************************************************************************/
36
40 namespace detail {
41 template<Integral TO, std::floating_point FROM>
42 constexpr TO roundAndCast(FROM x) {
43 if constexpr(!isBoolean<TO>) {
44 if(std::isnan(x)) {
45 if constexpr(std::is_signed_v<TO>) {
46 return std::numeric_limits<TO>::lowest();
47 }
48 return std::numeric_limits<TO>::max();
49 }
50 // NOLINTNEXTLINE(bugprone-incorrect-roundings)
51 return (x >= FROM(0)) ? TO(x + FROM(0.5)) : TO(x - FROM(0.5));
52 }
53
54 // Note: NaN will yield false
55 return x >= 0.5 || x <= -0.5;
56 }
57
58 /******************************************************************************************************************/
59
60 // Helper to check wether T_LEFT has a greater maximum value than T_RIGHT, without generating warnings due to
61 // unsigned/signed comparisons and precision loss in casts.
62 template<Arithmetic T_LEFT, Arithmetic T_RIGHT>
63 constexpr bool greaterMaximum() {
64 if constexpr(std::is_same_v<T_LEFT, T_RIGHT> || isBoolean<T_LEFT>) {
65 return false;
66 }
67 else if constexpr(isBoolean<T_RIGHT>) {
68 static_assert(!isBoolean<T_LEFT>);
69 return true;
70 }
71 else if constexpr(std::is_floating_point_v<T_LEFT>) {
72 return std::is_same_v<T_LEFT, double> || std::is_integral_v<T_RIGHT>;
73 }
74 else if constexpr(std::is_floating_point_v<T_RIGHT>) {
75 static_assert(std::is_integral_v<T_LEFT>);
76 return false;
77 }
78 else {
79 static_assert(std::is_integral_v<T_LEFT> && std::is_integral_v<T_RIGHT>);
80 return std::numeric_limits<T_LEFT>::max() > std::numeric_limits<T_RIGHT>::max();
81 }
82 }
83
84 /******************************************************************************************************************/
85
86 // Helper to check wether T_LEFT has a lesser (more negative) minimum value than T_RIGHT, without generating
87 // warnings due to precision loss in casts.
88 // This function only works for signed data types (unsigned is handled differently)
89 template<Arithmetic T_LEFT, Arithmetic T_RIGHT>
90 constexpr bool lesserMinimum() {
91 static_assert(std::is_signed_v<T_LEFT> && std::is_signed_v<T_RIGHT>);
92 if constexpr(std::is_same_v<T_LEFT, T_RIGHT>) {
93 return false;
94 }
95 else if constexpr(std::is_floating_point_v<T_LEFT>) {
96 return std::is_same_v<T_LEFT, double> || std::is_integral_v<T_RIGHT>;
97 }
98 else if constexpr(std::is_floating_point_v<T_RIGHT>) {
99 static_assert(std::is_integral_v<T_LEFT>);
100 return false;
101 }
102 else {
103 static_assert(std::is_integral_v<T_LEFT> && std::is_integral_v<T_RIGHT>);
104 return std::numeric_limits<T_LEFT>::lowest() < std::numeric_limits<T_RIGHT>::lowest();
105 }
106 }
107 } // namespace detail
108
109 /********************************************************************************************************************/
110
122 template<ArithmeticOrVoid TO, ArithmeticOrVoid FROM>
123 constexpr TO convert(FROM value) {
124 if constexpr(std::is_same_v<FROM, TO> || (isBoolean<FROM> && isBoolean<TO>)) {
125 // fastpath for equal types
126 return value;
127 }
128 if constexpr(std::is_same_v<FROM, ChimeraTK::Void> || std::is_same_v<TO, ChimeraTK::Void>) {
129 // fastpath for Void, also preventing compilation errors in the true numeric part below
130 return {};
131 }
132 else {
133 if constexpr(!std::is_floating_point_v<TO> || std::is_floating_point_v<FROM>) {
134 // Conversion into floating point works (almost) always as expected, so we need special handling for integer
135 // targets only
136
137 if constexpr(detail::greaterMaximum<FROM, TO>()) {
138 // clamp to maximum value
139 if(value >= FROM(std::numeric_limits<TO>::max())) {
140 if constexpr(std::is_floating_point_v<FROM> && std::is_floating_point_v<TO>) {
141 if(std::isinf(value)) {
142 return std::numeric_limits<TO>::infinity();
143 }
144 }
145 return std::numeric_limits<TO>::max();
146 }
147 }
148 if constexpr(std::is_signed_v<FROM> && !std::is_signed_v<TO> && !isBoolean<TO>) {
149 // clamp to 0 for unsigned target types
150 if(value < 0) {
151 return TO(0);
152 }
153 }
154 if constexpr(std::is_signed_v<FROM> && std::is_signed_v<TO>) {
155 if constexpr(detail::lesserMinimum<FROM, TO>()) {
156 // clamp to lowest for signed source and target
157 if(value <= FROM(std::numeric_limits<TO>::lowest())) {
158 if constexpr(std::is_floating_point_v<FROM> && std::is_floating_point_v<TO>) {
159 if(std::isinf(value)) {
160 return -std::numeric_limits<TO>::infinity();
161 }
162 }
163 return std::numeric_limits<TO>::lowest();
164 }
165 }
166 }
167
168 // Hint: no need for any clamping on minimum side for unsigned source!
169
170 if constexpr(std::is_floating_point_v<FROM> && !std::is_floating_point_v<TO>) {
171 // conversion from floating point into integer: need to round
172 return detail::roundAndCast<TO>(value);
173 }
174 }
175
176 return TO(value);
177 }
178 }
179
180 /********************************************************************************************************************/
181
182} // namespace ChimeraTK::numeric
Concept to require a numeric data type in templates.
Concept to require a numeric data type in templates, or ChimeraTK::Void.
Concept to require an integral data type incl.
constexpr bool greaterMaximum()
constexpr TO roundAndCast(FROM x)
constexpr TO convert(FROM value)
Convert numeric data types with proper rounding and clamping to the target value range.