Version: SMASH-3.4
numerics.h
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2018,2020,2022-2023,2025
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #ifndef SRC_INCLUDE_SMASH_NUMERICS_H_
11 #define SRC_INCLUDE_SMASH_NUMERICS_H_
12 
13 #include <algorithm>
14 #include <cassert>
15 #include <cmath>
16 #include <initializer_list>
17 #include <limits>
18 #include <type_traits>
19 
20 #include "constants.h"
21 
22 /**
23  * \file
24  *
25  * Generic numerical functions
26  *
27  * This file collects generic numerical functions such as for
28  * approximate equality checks between two floating point values.
29  */
30 
31 namespace smash {
32 
33 namespace detail {
34 
35 /**
36  * Compare whether two floating-point numbers are approximately equal à la Knuth
37  * up to a given tolerance. On top of Knuth's tolerance predicate some corner
38  * cases are treated and the caller can specify a threshold as last parameter to
39  * make the test consider numbers equal if the absolute value of their
40  * difference is below of it.
41  *
42  * \param[in] x First of the two numbers.
43  * \param[in] y Second of the two numbers.
44  * \param[in] epsilon The relative tolerance for the test.
45  * \param[in] threshold Threshold for the number comparison. By default this is
46  * zero, implying no threshold is considered.
47  *
48  * \return \c false if either \c x or \c y is not a finite number, provided that
49  * the type supports non-numeric representations (i.e. is infinite or NAN);
50  * \return \c true if <tt>x == y</tt>;
51  * \return \c true if <tt>x == 0</tt> and if \f$ |x| \le \varepsilon\f$;
52  * \return \c true if <tt>y == 0</tt> and if \f$ |y| \le \varepsilon\f$;
53  * \return \c true if \f$ |x - y| \le M_\mathrm{threshold} \f$;
54  * \return \c true if \f$ |x - y| \le \varepsilon \cdot \max(|x|, |y|) \f$
55  * (Knuth's tolerance predicate);
56  * \return \c false otherwise.
57  */
58 template <typename N, typename = std::enable_if_t<std::is_floating_point_v<N>>>
59 bool almost_equal_knuthish(const N x, const N y, const N epsilon,
60  const N threshold = N{0.0}) noexcept {
61  assert(epsilon > 0);
62  assert(threshold >= 0);
63  if constexpr (std::numeric_limits<N>::is_iec559) {
64  if (!std::isfinite(x) || !std::isfinite(y)) {
65  return false;
66  }
67  }
68  if (x == y)
69  return true;
70  else if (x == 0)
71  return std::abs(y) <= epsilon;
72  else if (y == 0)
73  return std::abs(x) <= epsilon;
74  else
75  return std::abs(x - y) <= threshold ||
76  std::abs(x - y) <= epsilon * std::max(std::abs(x), std::abs(y));
77 }
78 
79 } // namespace detail
80 
81 /**
82  * Checks whether two floating-point numbers are almost equal. This is done
83  * using \c smash::really_small as relative tolerance. All numbers are tested
84  * for equality, no matter which order of magnitude they have.
85  *
86  * \see detail::almost_equal_knuthish for more information.
87  */
88 template <typename N, typename = std::enable_if_t<std::is_floating_point_v<N>>>
89 bool almost_equal(const N x, const N y) {
90  return detail::almost_equal_knuthish<N>(x, y, static_cast<N>(really_small));
91 }
92 
93 /**
94  * Like \c smash::almost_equal, but using a less strict tolerance,
95  * <tt>smash::small_number</tt>. Furthermore, numbers smaller than a given
96  * threshold, <tt>smash::really_small</tt>, are now considered equal (in the
97  * sense that, for SMASH physics, their difference has no physical effect).
98  */
99 template <typename N, typename = std::enable_if_t<std::is_floating_point_v<N>>>
100 bool almost_equal_physics(const N x, const N y) {
101  const auto threshold = static_cast<N>(really_small);
102  const auto epsilon = static_cast<N>(small_number);
103  return detail::almost_equal_knuthish<N>(x, y, epsilon, threshold);
104 }
105 
106 /**
107  * \brief Returns whether any element in a collection is NaN.
108  *
109  * This function iterates through the elements of a collection and checks if any
110  * of them is NaN using \c std::isnan. NaN is a special floating-point value
111  * that represents undefined or unrepresentable values.
112  *
113  * \tparam T Iterable container of numeric values (defaults to \c
114  * std::initializer_list<double>).
115  * \param[in] collection The collection to be checked for NaN values.
116  *
117  * \return \c true if any element in the collection is NaN,
118  * \return \c false otherwise.
119  */
120 template <typename T = std::initializer_list<double>>
121 bool is_any_nan(const T& collection) {
122  for (const auto& number : collection) {
123  if (unlikely(std::isnan(number)))
124  return true;
125  }
126  return false;
127 }
128 
129 } // namespace smash
130 
131 #endif // SRC_INCLUDE_SMASH_NUMERICS_H_
Collection of useful constants that are known at compile time.
#define unlikely(x)
Tell the branch predictor that this expression is likely false.
Definition: macros.h:16
bool almost_equal_knuthish(const N x, const N y, const N epsilon, const N threshold=N{0.0}) noexcept
Compare whether two floating-point numbers are approximately equal à la Knuth up to a given tolerance...
Definition: numerics.h:59
Definition: action.h:24
bool almost_equal(const N x, const N y)
Checks whether two floating-point numbers are almost equal.
Definition: numerics.h:89
constexpr double small_number
Physical error tolerance.
Definition: constants.h:55
bool is_any_nan(const T &collection)
Returns whether any element in a collection is NaN.
Definition: numerics.h:121
bool almost_equal_physics(const N x, const N y)
Like smash::almost_equal, but using a less strict tolerance, smash::small_number.
Definition: numerics.h:100
constexpr double really_small
Numerical error tolerance.
Definition: constants.h:41