/* * mptBaseUtils.h * -------------- * Purpose: Various useful utility functions. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "BuildSettings.h" #include "mptBaseMacros.h" #include "mptBaseTypes.h" #include #if MPT_CXX_AT_LEAST(20) #include #endif #include #include #include #include #include #include #include #if MPT_COMPILER_MSVC #include #endif OPENMPT_NAMESPACE_BEGIN // cmath fixups #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif #ifndef M_LN2 #define M_LN2 0.69314718055994530942 #endif namespace mpt { template MPT_CONSTEXPR14_FUN std::array init_array(const Tx & x) { std::array result{}; for(std::size_t i = 0; i < N; ++i) { result[i] = x; } return result; } } // namespace mpt namespace mpt { // Work-around for the requirement of at least 1 non-throwing function argument combination in C++ (17,2a). template MPT_CONSTEXPR14_FUN bool constexpr_throw_helper(Exception && e, bool really = true) { //return !really ? really : throw std::forward(e); if(really) { throw std::forward(e); } // cppcheck-suppress identicalConditionAfterEarlyExit return really; } template MPT_CONSTEXPR14_FUN bool constexpr_throw(Exception && e) { return mpt::constexpr_throw_helper(std::forward(e)); } template constexpr T constexpr_throw_helper(Exception && e, bool really = true) { //return !really ? really : throw std::forward(e); if(really) { throw std::forward(e); } return T{}; } template constexpr T constexpr_throw(Exception && e) { return mpt::constexpr_throw_helper(std::forward(e)); } } // namespace mpt namespace mpt { // Modulo with more intuitive behaviour for some contexts: // Instead of being symmetrical around 0, the pattern for positive numbers is repeated in the negative range. // For example, wrapping_modulo(-1, m) == (m - 1). // Behaviour is undefined if m<=0. template MPT_CONSTEXPR11_FUN auto wrapping_modulo(T x, M m) -> decltype(x % m) { return (x >= 0) ? (x % m) : (m - 1 - ((-1 - x) % m)); } template MPT_CONSTEXPR11_FUN auto wrapping_divide(T x, D d) -> decltype(x / d) { return (x >= 0) ? (x / d) : (((x + 1) / d) - 1); } } // namespace mpt namespace mpt { // Saturate the value of src to the domain of Tdst template inline Tdst saturate_cast(Tsrc src) { // This code tries not only to obviously avoid overflows but also to avoid signed/unsigned comparison warnings and type truncation warnings (which in fact would be safe here) by explicit casting. static_assert(std::numeric_limits::is_integer); static_assert(std::numeric_limits::is_integer); if constexpr(std::numeric_limits::is_signed && std::numeric_limits::is_signed) { if constexpr(sizeof(Tdst) >= sizeof(Tsrc)) { return static_cast(src); } else { return static_cast(std::max(static_cast(std::numeric_limits::min()), std::min(src, static_cast(std::numeric_limits::max())))); } } else if constexpr(!std::numeric_limits::is_signed && !std::numeric_limits::is_signed) { if constexpr(sizeof(Tdst) >= sizeof(Tsrc)) { return static_cast(src); } else { return static_cast(std::min(src, static_cast(std::numeric_limits::max()))); } } else if constexpr(std::numeric_limits::is_signed && !std::numeric_limits::is_signed) { if constexpr(sizeof(Tdst) > sizeof(Tsrc)) { return static_cast(src); } else if constexpr(sizeof(Tdst) == sizeof(Tsrc)) { return static_cast(std::min(src, static_cast(std::numeric_limits::max()))); } else { return static_cast(std::min(src, static_cast(std::numeric_limits::max()))); } } else // Tdst unsigned, Tsrc signed { if constexpr(sizeof(Tdst) >= sizeof(Tsrc)) { return static_cast(std::max(static_cast(0), src)); } else { return static_cast(std::max(static_cast(0), std::min(src, static_cast(std::numeric_limits::max())))); } } } template inline Tdst saturate_cast(double src) { if(src >= static_cast(std::numeric_limits::max())) { return std::numeric_limits::max(); } if(src <= static_cast(std::numeric_limits::min())) { return std::numeric_limits::min(); } return static_cast(src); } template inline Tdst saturate_cast(float src) { if(src >= static_cast(std::numeric_limits::max())) { return std::numeric_limits::max(); } if(src <= static_cast(std::numeric_limits::min())) { return std::numeric_limits::min(); } return static_cast(src); } #if MPT_CXX_AT_LEAST(20) using std::popcount; using std::has_single_bit; using std::bit_ceil; using std::bit_floor; using std::bit_width; using std::rotl; using std::rotr; #else // C++20 header. // Note that we do not use SFINAE here but instead rely on static_assert. template MPT_CONSTEXPR14_FUN int popcount(T val) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); int result = 0; while(val > 0) { if(val & 0x1) { result++; } val >>= 1; } return result; } template MPT_CONSTEXPR14_FUN bool has_single_bit(T x) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); return mpt::popcount(x) == 1; } template MPT_CONSTEXPR14_FUN T bit_ceil(T x) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); T result = 1; while(result < x) { T newresult = result << 1; if(newresult < result) { return 0; } result = newresult; } return result; } template MPT_CONSTEXPR14_FUN T bit_floor(T x) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); if(x == 0) { return 0; } T result = 1; do { T newresult = result << 1; if(newresult < result) { return result; } result = newresult; } while(result <= x); return result >> 1; } template MPT_CONSTEXPR14_FUN T bit_width(T x) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); T result = 0; while(x > 0) { x >>= 1; result += 1; } return result; } namespace detail { template MPT_CONSTEXPR14_FUN T rotl(T x, int r) noexcept { auto N = std::numeric_limits::digits; return (x >> (N - r)) | (x << r); } template MPT_CONSTEXPR14_FUN T rotr(T x, int r) noexcept { auto N = std::numeric_limits::digits; return (x << (N - r)) | (x >> r); } } // namespace detail template MPT_CONSTEXPR14_FUN T rotl(T x, int s) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); auto N = std::numeric_limits::digits; auto r = s % N; return (s < 0) ? detail::rotr(x, -s) : ((x >> (N - r)) | (x << r)); } template MPT_CONSTEXPR14_FUN T rotr(T x, int s) noexcept { static_assert(std::numeric_limits::is_integer); static_assert(std::is_unsigned::value); auto N = std::numeric_limits::digits; auto r = s % N; return (s < 0) ? detail::rotl(x, -s) : ((x << (N - r)) | (x >> r)); } #endif } // namespace mpt namespace Util { namespace detail { template struct ModIfNotZeroImpl { template inline Tval mod(Tval x) { static_assert(std::numeric_limits::is_integer); static_assert(!std::numeric_limits::is_signed); static_assert(std::numeric_limits::is_integer); static_assert(!std::numeric_limits::is_signed); return static_cast(x % m); } }; template <> struct ModIfNotZeroImpl { template inline Tval mod(Tval x) { return x; } }; template <> struct ModIfNotZeroImpl { template inline Tval mod(Tval x) { return x; } }; template <> struct ModIfNotZeroImpl { template inline Tval mod(Tval x) { return x; } }; template <> struct ModIfNotZeroImpl { template inline Tval mod(Tval x) { return x; } }; } // namespace detail // Returns x % m if m != 0, x otherwise. // i.e. "return (m == 0) ? x : (x % m);", but without causing a warning with stupid older compilers template inline Tval ModIfNotZero(Tval x) { return detail::ModIfNotZeroImpl().mod(x); } // Returns true iff Tdst can represent the value val. // Use as if(Util::TypeCanHoldValue(-1)). template inline bool TypeCanHoldValue(Tsrc val) { return (static_cast(mpt::saturate_cast(val)) == val); } // Grows x with an exponential factor suitable for increasing buffer sizes. // Clamps the result at limit. // And avoids integer overflows while doing its business. // The growth factor is 1.5, rounding down, execpt for the initial x==1 case. template inline T ExponentialGrow(const T &x, const Tlimit &limit) { MPT_ASSERT(x > 0); MPT_ASSERT(limit > 0); if(x == 1) { return 2; } T add = std::min(x >> 1, std::numeric_limits::max() - x); return std::min(x + add, mpt::saturate_cast(limit)); } template inline T ExponentialGrow(const T &x) { return Util::ExponentialGrow(x, std::numeric_limits::max()); } } //namespace Util namespace mpt { template inline T sanitize_nan(T val) { static_assert(std::is_floating_point::value); if(std::isnan(val)) { return T(0.0); } return val; } template inline T safe_clamp(T v, T lo, T hi) { static_assert(std::is_floating_point::value); return std::clamp(mpt::sanitize_nan(v), lo, hi); } } // namespace mpt // Limits 'val' to given range. If 'val' is less than 'lowerLimit', 'val' is set to value 'lowerLimit'. // Similarly if 'val' is greater than 'upperLimit', 'val' is set to value 'upperLimit'. // If 'lowerLimit' > 'upperLimit', 'val' won't be modified. template inline void Limit(T& val, const C lowerLimit, const C upperLimit) { if(lowerLimit > upperLimit) return; if(val < lowerLimit) val = lowerLimit; else if(val > upperLimit) val = upperLimit; } // Like Limit, but returns value template inline T Clamp(T val, const C lowerLimit, const C upperLimit) { if(val < lowerLimit) return lowerLimit; else if(val > upperLimit) return upperLimit; else return val; } // Check if val is in [lo,hi] without causing compiler warnings // if theses checks are always true due to the domain of T. // GCC does not warn if the type is templated. template inline bool IsInRange(T val, C lo, C hi) { return lo <= val && val <= hi; } // Like Limit, but with upperlimit only. template inline void LimitMax(T& val, const C upperLimit) { if(val > upperLimit) val = upperLimit; } // Returns sign of a number (-1 for negative numbers, 1 for positive numbers, 0 for 0) template int sgn(T value) { return (value > T(0)) - (value < T(0)); } // mpt::rshift_signed // mpt::lshift_signed // Shift a signed integer value in a well-defined manner. // Does the same thing as MSVC would do. This is verified by the test suite. namespace mpt { template MPT_FORCEINLINE auto rshift_signed_standard(T x, int y) -> decltype(x >> y) { static_assert(std::numeric_limits::is_integer); static_assert(std::numeric_limits::is_signed); typedef decltype(x >> y) result_type; typedef typename std::make_unsigned::type unsigned_result_type; const unsigned_result_type roffset = static_cast(1) << ((sizeof(result_type) * 8) - 1); result_type rx = x; unsigned_result_type urx = static_cast(rx); urx += roffset; urx >>= y; urx -= roffset >> y; return static_cast(urx); } template MPT_FORCEINLINE auto lshift_signed_standard(T x, int y) -> decltype(x << y) { static_assert(std::numeric_limits::is_integer); static_assert(std::numeric_limits::is_signed); typedef decltype(x << y) result_type; typedef typename std::make_unsigned::type unsigned_result_type; const unsigned_result_type roffset = static_cast(1) << ((sizeof(result_type) * 8) - 1); result_type rx = x; unsigned_result_type urx = static_cast(rx); urx += roffset; urx <<= y; urx -= roffset << y; return static_cast(urx); } #if MPT_COMPILER_SHIFT_SIGNED template MPT_FORCEINLINE auto rshift_signed_undefined(T x, int y) -> decltype(x >> y) { static_assert(std::numeric_limits::is_integer); static_assert(std::numeric_limits::is_signed); return x >> y; } template MPT_FORCEINLINE auto lshift_signed_undefined(T x, int y) -> decltype(x << y) { static_assert(std::numeric_limits::is_integer); static_assert(std::numeric_limits::is_signed); return x << y; } template MPT_FORCEINLINE auto rshift_signed(T x, int y) -> decltype(x >> y) { return mpt::rshift_signed_undefined(x, y); } template MPT_FORCEINLINE auto lshift_signed(T x, int y) -> decltype(x << y) { return mpt::lshift_signed_undefined(x, y); } #else template MPT_FORCEINLINE auto rshift_signed(T x, int y) -> decltype(x >> y) { return mpt::rshift_signed_standard(x, y); } template MPT_FORCEINLINE auto lshift_signed(T x, int y) -> decltype(x << y) { return mpt::lshift_signed_standard(x, y); } #endif template struct array_size; template struct array_size> { static constexpr std::size_t size = N; }; template struct array_size { static constexpr std::size_t size = N; }; } // namespace mpt namespace Util { // Returns maximum value of given integer type. template constexpr T MaxValueOfType(const T&) {static_assert(std::numeric_limits::is_integer == true, "Only integer types are allowed."); return (std::numeric_limits::max)();} } // namespace Util namespace mpt { #if MPT_OS_DJGPP inline double round(const double& val) { return ::round(val); } inline float round(const float& val) { return ::roundf(val); } #else // !MPT_OS_DJGPP // C++11 std::round using std::round; #endif // MPT_OS_DJGPP // Rounds given double value to nearest integer value of type T. // Out-of-range values are saturated to the specified integer type's limits. template inline T saturate_round(double val) { static_assert(std::numeric_limits::is_integer == true, "Type is a not an integer"); return mpt::saturate_cast(mpt::round(val)); } template inline T saturate_round(float val) { static_assert(std::numeric_limits::is_integer == true, "Type is a not an integer"); return mpt::saturate_cast(mpt::round(val)); } } namespace Util { // Multiply two 32-bit integers, receive 64-bit result. // MSVC generates unnecessarily complicated code for the unoptimized variant using _allmul. MPT_FORCEINLINE int64 mul32to64(int32 a, int32 b) { #if MPT_COMPILER_MSVC && (defined(_M_IX86) || defined(_M_X64)) return __emul(a, b); #else return static_cast(a) * b; #endif } MPT_FORCEINLINE uint64 mul32to64_unsigned(uint32 a, uint32 b) { #if MPT_COMPILER_MSVC && (defined(_M_IX86) || defined(_M_X64)) return __emulu(a, b); #else return static_cast(a) * b; #endif } MPT_FORCEINLINE int32 muldiv(int32 a, int32 b, int32 c) { return mpt::saturate_cast( mul32to64( a, b ) / c ); } MPT_FORCEINLINE int32 muldivr(int32 a, int32 b, int32 c) { return mpt::saturate_cast( ( mul32to64( a, b ) + ( c / 2 ) ) / c ); } // Do not use overloading because catching unsigned version by accident results in slower X86 code. MPT_FORCEINLINE uint32 muldiv_unsigned(uint32 a, uint32 b, uint32 c) { return mpt::saturate_cast( mul32to64_unsigned( a, b ) / c ); } MPT_FORCEINLINE uint32 muldivr_unsigned(uint32 a, uint32 b, uint32 c) { return mpt::saturate_cast( ( mul32to64_unsigned( a, b ) + ( c / 2u ) ) / c ); } MPT_FORCEINLINE int32 muldivrfloor(int64 a, uint32 b, uint32 c) { a *= b; a += c / 2u; return (a >= 0) ? mpt::saturate_cast(a / c) : mpt::saturate_cast((a - (c - 1)) / c); } // rounds x up to multiples of target template inline T AlignUp(T x, T target) { return ((x + (target - 1)) / target) * target; } // rounds x down to multiples of target template inline T AlignDown(T x, T target) { return (x / target) * target; } } // namespace Util OPENMPT_NAMESPACE_END