|
| 1 | +// Copyright 2026 Matt Borland |
| 2 | +// Distributed under the Boost Software License, Version 1.0. |
| 3 | +// https://www.boost.org/LICENSE_1_0.txt |
| 4 | + |
| 5 | +#ifndef BOOST_SAFE_NUMBERS_CMATH_HPP |
| 6 | +#define BOOST_SAFE_NUMBERS_CMATH_HPP |
| 7 | + |
| 8 | +#include <boost/safe_numbers/floats.hpp> |
| 9 | +#include <boost/safe_numbers/signed_integers.hpp> |
| 10 | +#include <boost/safe_numbers/detail/type_traits.hpp> |
| 11 | + |
| 12 | +#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE |
| 13 | + |
| 14 | +#if (defined(BOOST_SAFE_NUMBERS_ENABLE_CUDA) && defined(__CUDACC__)) |
| 15 | +#include <cuda/std/cmath> |
| 16 | +#else |
| 17 | +#include <cmath> |
| 18 | +#endif |
| 19 | + |
| 20 | +#include <cstdint> |
| 21 | +#include <type_traits> |
| 22 | + |
| 23 | +#endif // BOOST_SAFE_NUMBERS_BUILD_MODULE |
| 24 | + |
| 25 | +// Namespace that actually provides the <cmath> functions. The wrappers delegate |
| 26 | +// to it with an explicit qualification so there is no ADL or self-recursion. |
| 27 | +#if (defined(BOOST_SAFE_NUMBERS_ENABLE_CUDA) && defined(__CUDACC__)) |
| 28 | +# define BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS cuda::std |
| 29 | +#else |
| 30 | +# define BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS std |
| 31 | +#endif |
| 32 | + |
| 33 | +namespace boost::safe_numbers { |
| 34 | + |
| 35 | +#define BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(fn) \ |
| 36 | +template <detail::non_bounded_float_library_type T> \ |
| 37 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto fn(const T x) -> T \ |
| 38 | +{ \ |
| 39 | + using underlying_type = detail::underlying_type_t<T>; \ |
| 40 | + return T{detail::impl::check_cmath_result<underlying_type>( \ |
| 41 | + BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::fn(static_cast<underlying_type>(x)), #fn)}; \ |
| 42 | +} |
| 43 | + |
| 44 | +#define BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(fn) \ |
| 45 | +template <detail::non_bounded_float_library_type T> \ |
| 46 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto fn(const T x, const T y) -> T \ |
| 47 | +{ \ |
| 48 | + using underlying_type = detail::underlying_type_t<T>; \ |
| 49 | + return T{detail::impl::check_cmath_result<underlying_type>( \ |
| 50 | + BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::fn(static_cast<underlying_type>(x), \ |
| 51 | + static_cast<underlying_type>(y)), #fn)}; \ |
| 52 | +} |
| 53 | + |
| 54 | +// Trigonometric |
| 55 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(sin) |
| 56 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(cos) |
| 57 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(tan) |
| 58 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(asin) |
| 59 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(acos) |
| 60 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(atan) |
| 61 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(atan2) |
| 62 | + |
| 63 | +// Hyperbolic |
| 64 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(sinh) |
| 65 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(cosh) |
| 66 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(tanh) |
| 67 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(asinh) |
| 68 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(acosh) |
| 69 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(atanh) |
| 70 | + |
| 71 | +// Exponential and logarithmic |
| 72 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(exp) |
| 73 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(exp2) |
| 74 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(expm1) |
| 75 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(log) |
| 76 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(log2) |
| 77 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(log10) |
| 78 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(log1p) |
| 79 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(logb) |
| 80 | + |
| 81 | +// Power and root |
| 82 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(sqrt) |
| 83 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(cbrt) |
| 84 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(pow) |
| 85 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(hypot) |
| 86 | + |
| 87 | +// Error and gamma |
| 88 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(erf) |
| 89 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(erfc) |
| 90 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(tgamma) |
| 91 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(lgamma) |
| 92 | + |
| 93 | +// Nearest integer (floating-point result) |
| 94 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(floor) |
| 95 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(ceil) |
| 96 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(trunc) |
| 97 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(round) |
| 98 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(nearbyint) |
| 99 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(rint) |
| 100 | + |
| 101 | +// Absolute value |
| 102 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(abs) |
| 103 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY(fabs) |
| 104 | + |
| 105 | +// Remainder, difference, min/max, sign manipulation |
| 106 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(fmod) |
| 107 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(remainder) |
| 108 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(fdim) |
| 109 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(fmin) |
| 110 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(fmax) |
| 111 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(copysign) |
| 112 | +BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY(nextafter) |
| 113 | + |
| 114 | +// Fused multiply-add |
| 115 | +template <detail::non_bounded_float_library_type T> |
| 116 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto fma(const T x, const T y, const T z) -> T |
| 117 | +{ |
| 118 | + using underlying_type = detail::underlying_type_t<T>; |
| 119 | + return T{detail::impl::check_cmath_result<underlying_type>( |
| 120 | + BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::fma(static_cast<underlying_type>(x), |
| 121 | + static_cast<underlying_type>(y), |
| 122 | + static_cast<underlying_type>(z)), "fma")}; |
| 123 | +} |
| 124 | + |
| 125 | +// Scaling by a power of two (integer exponent input, floating-point result) |
| 126 | +template <detail::non_bounded_float_library_type T> |
| 127 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto ldexp(const T x, const int exp) -> T |
| 128 | +{ |
| 129 | + using underlying_type = detail::underlying_type_t<T>; |
| 130 | + return T{detail::impl::check_cmath_result<underlying_type>( |
| 131 | + BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::ldexp(static_cast<underlying_type>(x), exp), "ldexp")}; |
| 132 | +} |
| 133 | + |
| 134 | +template <detail::non_bounded_float_library_type T> |
| 135 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto scalbn(const T x, const int exp) -> T |
| 136 | +{ |
| 137 | + using underlying_type = detail::underlying_type_t<T>; |
| 138 | + return T{detail::impl::check_cmath_result<underlying_type>( |
| 139 | + BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::scalbn(static_cast<underlying_type>(x), exp), "scalbn")}; |
| 140 | +} |
| 141 | + |
| 142 | +template <detail::non_bounded_float_library_type T> |
| 143 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto scalbln(const T x, const long exp) -> T |
| 144 | +{ |
| 145 | + using underlying_type = detail::underlying_type_t<T>; |
| 146 | + return T{detail::impl::check_cmath_result<underlying_type>( |
| 147 | + BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::scalbln(static_cast<underlying_type>(x), exp), "scalbln")}; |
| 148 | +} |
| 149 | + |
| 150 | +// ------------------------------ |
| 151 | +// Classification predicates |
| 152 | +// ------------------------------ |
| 153 | +// These inspect a value and never throw, so they are the escape hatch for code |
| 154 | +// that needs to reason about infinities and NANs. They are genuinely constexpr |
| 155 | +// because they delegate to the library's own constexpr classification helpers. |
| 156 | + |
| 157 | +template <detail::non_bounded_float_library_type T> |
| 158 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isnan(const T x) noexcept -> bool |
| 159 | +{ |
| 160 | + return detail::impl::constexpr_isnan(static_cast<detail::underlying_type_t<T>>(x)); |
| 161 | +} |
| 162 | + |
| 163 | +template <detail::non_bounded_float_library_type T> |
| 164 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isinf(const T x) noexcept -> bool |
| 165 | +{ |
| 166 | + return detail::impl::constexpr_isinf(static_cast<detail::underlying_type_t<T>>(x)); |
| 167 | +} |
| 168 | + |
| 169 | +template <detail::non_bounded_float_library_type T> |
| 170 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isfinite(const T x) noexcept -> bool |
| 171 | +{ |
| 172 | + const auto v {static_cast<detail::underlying_type_t<T>>(x)}; |
| 173 | + return !detail::impl::constexpr_isinf(v) && !detail::impl::constexpr_isnan(v); |
| 174 | +} |
| 175 | + |
| 176 | +template <detail::non_bounded_float_library_type T> |
| 177 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isnormal(const T x) noexcept -> bool |
| 178 | +{ |
| 179 | + return detail::impl::constexpr_isnormal(static_cast<detail::underlying_type_t<T>>(x)); |
| 180 | +} |
| 181 | + |
| 182 | +template <detail::non_bounded_float_library_type T> |
| 183 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto signbit(const T x) noexcept -> bool |
| 184 | +{ |
| 185 | + return detail::impl::constexpr_signbit(static_cast<detail::underlying_type_t<T>>(x)); |
| 186 | +} |
| 187 | + |
| 188 | +template <detail::non_bounded_float_library_type T> |
| 189 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto fpclassify(const T x) noexcept -> int |
| 190 | +{ |
| 191 | + return detail::impl::constexpr_fpclassify(static_cast<detail::underlying_type_t<T>>(x)); |
| 192 | +} |
| 193 | + |
| 194 | +// Non-signaling comparison predicates: ordered comparisons that return false |
| 195 | +// (rather than raising) when an operand is NAN. |
| 196 | + |
| 197 | +template <detail::non_bounded_float_library_type T> |
| 198 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isunordered(const T x, const T y) noexcept -> bool |
| 199 | +{ |
| 200 | + using underlying_type = detail::underlying_type_t<T>; |
| 201 | + return detail::impl::constexpr_isnan(static_cast<underlying_type>(x)) || |
| 202 | + detail::impl::constexpr_isnan(static_cast<underlying_type>(y)); |
| 203 | +} |
| 204 | + |
| 205 | +template <detail::non_bounded_float_library_type T> |
| 206 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isgreater(const T x, const T y) noexcept -> bool |
| 207 | +{ |
| 208 | + using underlying_type = detail::underlying_type_t<T>; |
| 209 | + return !isunordered(x, y) && static_cast<underlying_type>(x) > static_cast<underlying_type>(y); |
| 210 | +} |
| 211 | + |
| 212 | +template <detail::non_bounded_float_library_type T> |
| 213 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isgreaterequal(const T x, const T y) noexcept -> bool |
| 214 | +{ |
| 215 | + using underlying_type = detail::underlying_type_t<T>; |
| 216 | + return !isunordered(x, y) && static_cast<underlying_type>(x) >= static_cast<underlying_type>(y); |
| 217 | +} |
| 218 | + |
| 219 | +template <detail::non_bounded_float_library_type T> |
| 220 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto isless(const T x, const T y) noexcept -> bool |
| 221 | +{ |
| 222 | + using underlying_type = detail::underlying_type_t<T>; |
| 223 | + return !isunordered(x, y) && static_cast<underlying_type>(x) < static_cast<underlying_type>(y); |
| 224 | +} |
| 225 | + |
| 226 | +template <detail::non_bounded_float_library_type T> |
| 227 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto islessequal(const T x, const T y) noexcept -> bool |
| 228 | +{ |
| 229 | + using underlying_type = detail::underlying_type_t<T>; |
| 230 | + return !isunordered(x, y) && static_cast<underlying_type>(x) <= static_cast<underlying_type>(y); |
| 231 | +} |
| 232 | + |
| 233 | +template <detail::non_bounded_float_library_type T> |
| 234 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto islessgreater(const T x, const T y) noexcept -> bool |
| 235 | +{ |
| 236 | + using underlying_type = detail::underlying_type_t<T>; |
| 237 | + const auto a {static_cast<underlying_type>(x)}; |
| 238 | + const auto b {static_cast<underlying_type>(y)}; |
| 239 | + return !isunordered(x, y) && (a < b || a > b); |
| 240 | +} |
| 241 | + |
| 242 | +// ------------------------------ |
| 243 | +// Integer-returning functions |
| 244 | +// ------------------------------ |
| 245 | +// These return the safe integer type that matches the width of the std return |
| 246 | +// type. A non-finite input has an unspecified std result (and raises FE_INVALID), |
| 247 | +// so it is reported as a domain error instead. Not constexpr: the underlying std |
| 248 | +// functions are not constexpr in C++20/23. |
| 249 | + |
| 250 | +template <detail::non_bounded_float_library_type T> |
| 251 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto lround(const T x) -> i64 |
| 252 | +{ |
| 253 | + using underlying_type = detail::underlying_type_t<T>; |
| 254 | + const auto v {static_cast<underlying_type>(x)}; |
| 255 | + if (detail::impl::constexpr_isnan(v) || detail::impl::constexpr_isinf(v)) |
| 256 | + { |
| 257 | + detail::impl::throw_cmath_domain<underlying_type>("lround"); |
| 258 | + } |
| 259 | + return i64{static_cast<std::int64_t>(BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::lround(v))}; |
| 260 | +} |
| 261 | + |
| 262 | +template <detail::non_bounded_float_library_type T> |
| 263 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto llround(const T x) -> i64 |
| 264 | +{ |
| 265 | + using underlying_type = detail::underlying_type_t<T>; |
| 266 | + const auto v {static_cast<underlying_type>(x)}; |
| 267 | + if (detail::impl::constexpr_isnan(v) || detail::impl::constexpr_isinf(v)) |
| 268 | + { |
| 269 | + detail::impl::throw_cmath_domain<underlying_type>("llround"); |
| 270 | + } |
| 271 | + return i64{static_cast<std::int64_t>(BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::llround(v))}; |
| 272 | +} |
| 273 | + |
| 274 | +template <detail::non_bounded_float_library_type T> |
| 275 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto lrint(const T x) -> i64 |
| 276 | +{ |
| 277 | + using underlying_type = detail::underlying_type_t<T>; |
| 278 | + const auto v {static_cast<underlying_type>(x)}; |
| 279 | + if (detail::impl::constexpr_isnan(v) || detail::impl::constexpr_isinf(v)) |
| 280 | + { |
| 281 | + detail::impl::throw_cmath_domain<underlying_type>("lrint"); |
| 282 | + } |
| 283 | + return i64{static_cast<std::int64_t>(BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::lrint(v))}; |
| 284 | +} |
| 285 | + |
| 286 | +template <detail::non_bounded_float_library_type T> |
| 287 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto llrint(const T x) -> i64 |
| 288 | +{ |
| 289 | + using underlying_type = detail::underlying_type_t<T>; |
| 290 | + const auto v {static_cast<underlying_type>(x)}; |
| 291 | + if (detail::impl::constexpr_isnan(v) || detail::impl::constexpr_isinf(v)) |
| 292 | + { |
| 293 | + detail::impl::throw_cmath_domain<underlying_type>("llrint"); |
| 294 | + } |
| 295 | + return i64{static_cast<std::int64_t>(BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::llrint(v))}; |
| 296 | +} |
| 297 | + |
| 298 | +template <detail::non_bounded_float_library_type T> |
| 299 | +BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto ilogb(const T x) -> i32 |
| 300 | +{ |
| 301 | + using underlying_type = detail::underlying_type_t<T>; |
| 302 | + const auto v {static_cast<underlying_type>(x)}; |
| 303 | + const auto cls {detail::impl::constexpr_fpclassify(v)}; |
| 304 | + if (cls == FP_NAN || cls == FP_INFINITE || cls == FP_ZERO) |
| 305 | + { |
| 306 | + detail::impl::throw_cmath_domain<underlying_type>("ilogb"); |
| 307 | + } |
| 308 | + return i32{static_cast<std::int32_t>(BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS::ilogb(v))}; |
| 309 | +} |
| 310 | + |
| 311 | +#undef BOOST_SAFE_NUMBERS_DETAIL_FLOAT_UNARY |
| 312 | +#undef BOOST_SAFE_NUMBERS_DETAIL_FLOAT_BINARY |
| 313 | + |
| 314 | +} // namespace boost::safe_numbers |
| 315 | + |
| 316 | +#undef BOOST_SAFE_NUMBERS_DETAIL_CMATH_NS |
| 317 | + |
| 318 | +#endif // BOOST_SAFE_NUMBERS_CMATH_HPP |
0 commit comments