/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright 2010-2018 Danny Robson */ #ifndef __MATHS_HPP #define __MATHS_HPP // DO NOT INCLUDE debug.hpp // it triggers a circular dependency; debug -> format -> maths -> debug // instead, just use cassert #include "types/traits.hpp" #include "float.hpp" #include #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // NOTE: You may be tempted to add all sorts of performance enhancing // attributes (like gnu::const or gnu::pure). DO NOT DO THIS WITHOUT EXTENSIVE // TESTING. Just about everything will break in some way with these attributes. // // In particular: it is safest to apply these only to leaf functions /////////////////////////////////////////////////////////////////////////////// namespace util { /////////////////////////////////////////////////////////////////////////// template std::enable_if_t, T> abs [[gnu::const]] (T t) { return t > 0 ? t : -t; } //----------------------------------------------------------------------------- // Useful for explictly ignore equality warnings #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" template constexpr typename std::enable_if_t< std::is_arithmetic::value && std::is_arithmetic::value, bool > equal (const Ta &a, const Tb &b) { // use double not to force conversion for types with explicit bool // operators on the result of equality return !!(a == b); } //------------------------------------------------------------------------- template inline typename std::enable_if_t< !std::is_arithmetic::value || !std::is_arithmetic::value, bool > equal (const Ta &a, const Tb &b) { // use double not to force conversion for types with explicit bool // operators on the result of equality return !!(a == b); } #pragma GCC diagnostic pop /////////////////////////////////////////////////////////////////////////// // Comparisons inline bool relatively_equal (float truth, float test, float percentage) { // we want to do 1 - b / a, but a might be zero. if we have FE_INVALID // enabled then we'll pretty quickly throw an exception. // // instead we use |a - b | / (1 + |truth|). note that it's not as // accurate when the test values aren't close to 1. return std::abs (truth - test) / (1 + std::abs (truth)) < percentage; } //------------------------------------------------------------------------- inline bool almost_equal (float a, float b) { return ieee_single::almost_equal (a, b); } //----------------------------------------------------------------------------- inline bool almost_equal (double a, double b) { return ieee_double::almost_equal (a, b); } //----------------------------------------------------------------------------- template constexpr auto almost_equal (const ValueA &a, const ValueB &b) { if constexpr (std::is_floating_point_v && std::is_floating_point_v) { using common_t = std::common_type_t; return almost_equal (common_t {a}, common_t{b}); } else if constexpr (std::is_integral_v && std::is_integral_v && std::is_signed_v == std::is_signed_v) { using common_t = std::common_type_t; return static_cast (a) == static_cast (b); } else { return equal (a, b); } } //----------------------------------------------------------------------------- template constexpr std::enable_if_t< std::is_integral::value, bool > almost_zero (T t) { return t == 0; } //------------------------------------------------------------------------- template std::enable_if_t< !std::is_integral::value, bool > almost_zero (T a) { return almost_equal (a, T{0}); } //------------------------------------------------------------------------- template constexpr typename std::enable_if_t< std::is_integral::value, bool > exactly_zero (T t) { return equal (t, T{0}); } template constexpr typename std::enable_if_t< !std::is_integral::value, bool > exactly_zero (T t) { return equal (t, T{0}); } //------------------------------------------------------------------------- template constexpr typename std::enable_if_t< std::is_floating_point::value, bool > is_nan (T t) { return std::isnan (t); } //------------------------------------------------------------------------- template constexpr typename std::enable_if_t< !std::is_floating_point::value, bool > is_nan (const T&) { return false; } /////////////////////////////////////////////////////////////////////////// // exponentials template < typename BaseT, typename ExponentT, typename = std::enable_if_t< std::is_integral_v, void > > constexpr BaseT pow [[gnu::const]] (BaseT base, ExponentT exponent) { assert (exponent >= 0); if (exponent == 1) return base; if (exponent == 0) return BaseT{1}; return base * pow (base, exponent - 1); } //------------------------------------------------------------------------- template constexpr std::enable_if_t::value, bool> is_pow2 [[gnu::const]] (T value) { return value && !(value & (value - 1)); } //----------------------------------------------------------------------------- // Logarithms template constexpr std::enable_if_t, T> log2 (T val) { T tally = 0; while (val >>= 1) ++tally; return tally; } //------------------------------------------------------------------------- template T log2up (T val); /////////////////////////////////////////////////////////////////////////////// /// round T up to the nearest multiple of U template inline typename std::common_type< std::enable_if_t::value,T>, std::enable_if_t::value,U> >::type round_up (T value, U size) { // we perform this as two steps to avoid unnecessarily incrementing when // remainder is zero. if (value % size) value += size - value % size; return value; } ///---------------------------------------------------------------------------- /// round T up to the nearest power-of-2 template constexpr std::enable_if_t< std::is_integral::value, T > round_pow2 (T value) { using return_type = std::enable_if_t::value, T>; --value; for (unsigned i = 1; i < sizeof (T) * 8; i <<= 1) { value |= value >> i; } ++value; return (return_type)value; } ///---------------------------------------------------------------------------- /// round T up to the nearest multiple of U and return the quotient. template constexpr std::enable_if_t< std::is_integral::value && std::is_integral::value, T > divup (const T a, const U b) { return (a + b - 1) / b; } /////////////////////////////////////////////////////////////////////////////// // Properties template constexpr std::enable_if_t::value, bool> is_integer (T) { return true; } template constexpr std::enable_if_t::value, bool> is_integer (T t) { T i = 0; return equal (std::modf (t, &i), T{0}); } //------------------------------------------------------------------------- template < typename NumericT, typename = std::enable_if_t< std::is_integral_v, void > > constexpr auto digits10 (NumericT v) noexcept { // cascading conditionals are faster, but it's super annoying to write // out for arbitrarily sized types so we use this base case unti // there's actually a performance reason to use another algorithm. int tally = 0; do { v /= 10; ++tally; } while (v); return tally; /* return (v >= 1000000000) ? 10 : (v >= 100000000) ? 9 : (v >= 10000000) ? 8 : (v >= 1000000) ? 7 : (v >= 100000) ? 6 : (v >= 10000) ? 5 : (v >= 1000) ? 4 : (v >= 100) ? 3 : (v >= 10) ? 2 : 1; */ } template constexpr std::enable_if_t< std::is_integral_v && std::is_integral_v, int > digits (ValueT value, BaseT base) noexcept { assert (base > 0); if (value < 0) value *= -1; int tally = 1; while (value /= base) ++tally; return tally; } ///---------------------------------------------------------------------------- /// return positive or negative unit value corresponding to the input. template constexpr std::enable_if_t< std::is_signed::value && std::is_integral::value, T > sign (T t) { return t < 0 ? -1 : 1; } ///------------------------------------------------------------------------ /// return positive or negative unit value corresponding to the input. /// guaranteed to give correct results for signed zeroes, use another /// method if extreme speed is important. template constexpr std::enable_if_t< std::is_floating_point::value, T > sign (T t) { return std::signbit (t) ? -1 : 1; } //------------------------------------------------------------------------- template constexpr bool samesign (T a, T b) { return (a >= 0 && b >= 0) || (a <= 0 && b <= 0); } /////////////////////////////////////////////////////////////////////////////// // factorisation template const T& identity (const T& t) { return t; } /////////////////////////////////////////////////////////////////////////// // Modulus/etc // namespaced wrapper for `man 3 fmod` template constexpr std::enable_if_t< std::is_floating_point::value, T > mod (T x, T y) { return std::fmod (x, y); } template constexpr std::enable_if_t< std::is_integral::value, T > mod (T x, T y) { return x % y; } /////////////////////////////////////////////////////////////////////////////// // angles, trig namespace detail { template struct pi; template <> struct pi { static constexpr float value = 3.141592653589793238462643f; }; template <> struct pi { static constexpr double value = 3.141592653589793238462643; }; }; template constexpr auto pi = detail::pi::value; //----------------------------------------------------------------------------- template constexpr T E = static_cast (2.71828182845904523536028747135266250); //----------------------------------------------------------------------------- template constexpr T to_degrees (T radians) { static_assert (std::is_floating_point::value, "undefined for integral types"); return radians * 180 / pi; } //----------------------------------------------------------------------------- template constexpr T to_radians (T degrees) { static_assert (std::is_floating_point::value, "undefined for integral types"); return degrees / 180 * pi; } //----------------------------------------------------------------------------- //! Normalised sinc function template constexpr T sincn (T x) { return almost_zero (x) ? 1 : std::sin (pi * x) / (pi * x); } //----------------------------------------------------------------------------- //! Unnormalised sinc function template constexpr T sincu (T x) { return almost_zero (x) ? 1 : std::sin (x) / x; } //------------------------------------------------------------------------- // thin wrappers around std trig identities. // // we have these because it's a little easier to qualify templates when // passing function objects as compared to explicitly disambiguating raw // functions (ie, with casts or typedefs). template ValueT cos (ValueT theta) { return ::std::cos (theta); } template ValueT sin (ValueT theta) { return ::std::sin (theta); } template ValueT tan (ValueT theta) { return ::std::tan (theta); } /////////////////////////////////////////////////////////////////////////////// // combinatorics constexpr uintmax_t factorial (unsigned i) { return i <= 1 ? 0 : i * factorial (i - 1); } //----------------------------------------------------------------------------- /// stirlings approximation of factorials inline uintmax_t stirling (unsigned n) { using real_t = double; return static_cast ( std::sqrt (2 * pi * n) * std::pow (n / E, n) ); } //----------------------------------------------------------------------------- constexpr uintmax_t combination (unsigned n, unsigned k) { return factorial (n) / (factorial (k) / (factorial (n - k))); } /////////////////////////////////////////////////////////////////////////////// // kahan summation for long floating point sequences template std::enable_if_t< std::is_floating_point< typename std::iterator_traits::value_type >::value, typename std::iterator_traits::value_type > sum (InputT first, InputT last) { using T = typename std::iterator_traits::value_type; T sum = 0; T c = 0; for (auto cursor = first; cursor != last; ++cursor) { // Infinities are handled poorly in this implementation. We tend // to produce NaNs because of the subtraction where we compute // `c'. For the time being just panic in this scenario. assert(!std::isinf (*cursor)); T y = *cursor - c; T t = sum + y; c = (t - sum) - y; sum = t; } return sum; } //------------------------------------------------------------------------- template std::enable_if_t< std::is_integral< typename std::iterator_traits::value_type >::value, typename std::iterator_traits::value_type > sum (InputT first, InputT last) { using T = typename std::iterator_traits::value_type; return std::accumulate (first, last, T{0}); } /////////////////////////////////////////////////////////////////////////// /// Variadic minimum // disable the single parameter version for non-arithmetic types so that // min for coord types is unambiguous. allow pointers too because it // doesn't add ambiguity and it simplifies some memory juggling. template < typename T, typename = std::enable_if_t< std::is_arithmetic_v || std::is_pointer_v, void > > constexpr T min (const T a) { return a; } //------------------------------------------------------------------------- template constexpr std::enable_if_t< std::is_unsigned>::value == std::is_unsigned>::value && std::is_integral>::value == std::is_integral>::value, std::common_type_t > min (const T a, const U b, Args ...args) { return min (a < b ? a : b, std::forward (args)...); } //------------------------------------------------------------------------- /// Variadic maximum template constexpr std::enable_if_t || std::is_pointer_v, T> max (const T a) { return a; } //------------------------------------------------------------------------- template constexpr std::enable_if_t< std::is_unsigned>::value == std::is_unsigned>::value && std::is_integral>::value == std::is_integral>::value, std::common_type_t > max (const T a, const U b, Args ...args) { return max (a > b ? a : b, std::forward (args)...); } /////////////////////////////////////////////////////////////////////////// // Limiting functions // min/max clamping template constexpr std::enable_if_t< std::is_scalar_v && std::is_scalar_v && std::is_scalar_v, std::common_type_t > limit (const T val, const U lo, const V hi) { assert (lo <= hi); return val > hi ? hi: val < lo ? lo: val; } //------------------------------------------------------------------------- // clamped cubic hermite interpolation template constexpr T smoothstep (T a, T b, T x) { assert (a <= b); x = limit ((x - a) / (b - a), T{0}, T{1}); return x * x * (3 - 2 * x); } //------------------------------------------------------------------------- template constexpr std::enable_if_t::value, U> mix (U a, U b, T t) { // give some tolerance for floating point rounding assert (t >= -0.00001f); assert (t <= 1.00001f); return a * (1 - t) + b * t; } /////////////////////////////////////////////////////////////////////////// /// convert between different representations of normalised quantities. /// /// * floating point values must be within [0, 1] (otherwise undefined) /// * signed values are handled by converting to unsigned representations /// * may introduce small biases when expanding values so that low order /// bits have some meaning (particularly when dealing with UINTMAX) // uint -> float template constexpr typename std::enable_if< std::is_unsigned::value && std::is_floating_point::value, U >::type renormalise (T t) { return t / static_cast (std::numeric_limits::max ()); } //------------------------------------------------------------------------- // float -> uint template constexpr typename std::enable_if< std::is_floating_point::value && std::is_unsigned::value, U >::type renormalise (T t) { // Ideally std::ldexp would be involved but it complicates handing // integers with greater precision than our floating point type. Also it // would prohibit constexpr and involve errno. size_t usable = std::numeric_limits::digits; size_t available = sizeof (U) * 8; size_t shift = std::max (available, usable) - usable; t = limit (t, 0, 1); // construct an integer of the float's mantissa size, multiply it by our // parameter, then shift it back into the full range of the integer type. U in = std::numeric_limits::max () >> shift; U mid = static_cast (t * in); U out = mid << shift; // use the top bits of the output to fill the bottom bits which through // shifting would otherwise be zero. this gives us the full extent of the // integer range, while varying predictably through the entire output // space. return out | out >> (available - shift); } //------------------------------------------------------------------------- // float -> float, avoids identity conversion as we don't want to create // ambiguous overloads template constexpr typename std::enable_if< std::is_floating_point::value && std::is_floating_point::value && !std::is_same::value, U >::type renormalise (T t) { return static_cast (t); } //------------------------------------------------------------------------- // hi_uint -> lo_uint template constexpr typename std::enable_if< std::is_unsigned::value && std::is_unsigned::value && (sizeof (T) > sizeof (U)), U >::type renormalise (T t) { static_assert (sizeof (T) > sizeof (U), "assumes right shift is sufficient"); // we have excess bits ,just shift and return constexpr auto shift = 8 * (sizeof (T) - sizeof (U)); return t >> shift; } //------------------------------------------------------------------------- // lo_uint -> hi_uint template < typename SrcT, typename DstT, typename = std::enable_if_t< std::is_unsigned::value && std::is_unsigned::value && sizeof (SrcT) < sizeof (DstT), DstT > > constexpr DstT renormalise (SrcT src) { // we can make some simplifying assumptions for the shift distances if // we assume the integers are powers of two. this is probably going to // be the case for every conceivable input type, but we don't want to // get caught out if we extend this routine to more general types // (eg, OpenGL DEPTH24). static_assert (is_pow2 (sizeof (SrcT))); static_assert (is_pow2 (sizeof (DstT))); static_assert (sizeof (SrcT) < sizeof (DstT), "assumes bit creation is required to fill space"); // we need to create bits. fill the output integer with copies of ourself. // this is approximately correct in the general case (introducing a small // linear positive bias), but it allows us to set all output bits high // when we receive the maximum allowable input value. static_assert (sizeof (DstT) % sizeof (SrcT) == 0, "assumes integer multiple of sizes"); // clang#xxxx: ideally we wouldn't use a multiplication here, but we // trigger a segfault in clang-5.0 when using ld.gold+lto; // 'X86 DAG->DAG Instruction Selection' // // create a mask of locations we'd like copies of the src bit pattern. // // this replicates repeatedly or'ing and shifting dst with itself. DstT dst { 1 }; for (unsigned i = sizeof (SrcT) * 8; i < sizeof (DstT) * 8; i *= 2) dst |= dst << i; return dst * src; } //------------------------------------------------------------------------- // identity transformation. must precede the signed cases, as they may rely // on this as a side effect of casts. template constexpr typename std::enable_if< std::is_same::value, U >::type renormalise (T t) { return t; } //------------------------------------------------------------------------- // anything-to-sint template constexpr typename std::enable_if< std::is_signed::value && std::is_integral::value && !std::is_same::value, U >::type renormalise (T t) { using uint_t = typename std::make_unsigned::type; return static_cast ( ::util::renormalise (t) + std::numeric_limits::min () ); }; //------------------------------------------------------------------------- // sint-to-anything template constexpr typename std::enable_if< std::is_signed::value && std::is_integral::value && !std::is_same::value, U >::type renormalise (T sint) { using uint_t = typename std::make_unsigned::type; return ::util::renormalise ( static_cast (sint) - std::numeric_limits::min () ); }; } #endif // __MATHS_HPP