2021-12-26 11:29:43 +00:00
/*
* 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 <algorithm>
# if MPT_CXX_AT_LEAST(20)
# include <bit>
# endif
# include <limits>
# include <numeric>
# include <utility>
# include <cmath>
# include <cstdlib>
# include <math.h>
# include <stdlib.h>
# if MPT_COMPILER_MSVC
# include <intrin.h>
# 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 < typename T , std : : size_t N , typename Tx >
MPT_CONSTEXPR14_FUN std : : array < T , N > init_array ( const Tx & x )
{
std : : array < T , N > 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 < typename Exception >
MPT_CONSTEXPR14_FUN bool constexpr_throw_helper ( Exception & & e , bool really = true )
{
//return !really ? really : throw std::forward<Exception>(e);
if ( really )
{
throw std : : forward < Exception > ( e ) ;
}
// cppcheck-suppress identicalConditionAfterEarlyExit
return really ;
}
template < typename Exception >
MPT_CONSTEXPR14_FUN bool constexpr_throw ( Exception & & e )
{
return mpt : : constexpr_throw_helper ( std : : forward < Exception > ( e ) ) ;
}
template < typename T , typename Exception >
constexpr T constexpr_throw_helper ( Exception & & e , bool really = true )
{
//return !really ? really : throw std::forward<Exception>(e);
if ( really )
{
throw std : : forward < Exception > ( e ) ;
}
return T { } ;
}
template < typename T , typename Exception >
constexpr T constexpr_throw ( Exception & & e )
{
return mpt : : constexpr_throw_helper < T > ( std : : forward < Exception > ( 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 < typename T , typename M >
MPT_CONSTEXPR11_FUN auto wrapping_modulo ( T x , M m ) - > decltype ( x % m )
{
return ( x > = 0 ) ? ( x % m ) : ( m - 1 - ( ( - 1 - x ) % m ) ) ;
}
template < typename T , typename D >
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 < typename Tdst , typename Tsrc >
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 < Tdst > : : is_integer ) ;
static_assert ( std : : numeric_limits < Tsrc > : : is_integer ) ;
if constexpr ( std : : numeric_limits < Tdst > : : is_signed & & std : : numeric_limits < Tsrc > : : is_signed )
{
if constexpr ( sizeof ( Tdst ) > = sizeof ( Tsrc ) )
{
return static_cast < Tdst > ( src ) ;
} else
{
return static_cast < Tdst > ( std : : max ( static_cast < Tsrc > ( std : : numeric_limits < Tdst > : : min ( ) ) , std : : min ( src , static_cast < Tsrc > ( std : : numeric_limits < Tdst > : : max ( ) ) ) ) ) ;
}
} else if constexpr ( ! std : : numeric_limits < Tdst > : : is_signed & & ! std : : numeric_limits < Tsrc > : : is_signed )
{
if constexpr ( sizeof ( Tdst ) > = sizeof ( Tsrc ) )
{
return static_cast < Tdst > ( src ) ;
} else
{
return static_cast < Tdst > ( std : : min ( src , static_cast < Tsrc > ( std : : numeric_limits < Tdst > : : max ( ) ) ) ) ;
}
} else if constexpr ( std : : numeric_limits < Tdst > : : is_signed & & ! std : : numeric_limits < Tsrc > : : is_signed )
{
if constexpr ( sizeof ( Tdst ) > sizeof ( Tsrc ) )
{
return static_cast < Tdst > ( src ) ;
} else if constexpr ( sizeof ( Tdst ) = = sizeof ( Tsrc ) )
{
return static_cast < Tdst > ( std : : min ( src , static_cast < Tsrc > ( std : : numeric_limits < Tdst > : : max ( ) ) ) ) ;
} else
{
return static_cast < Tdst > ( std : : min ( src , static_cast < Tsrc > ( std : : numeric_limits < Tdst > : : max ( ) ) ) ) ;
}
} else // Tdst unsigned, Tsrc signed
{
if constexpr ( sizeof ( Tdst ) > = sizeof ( Tsrc ) )
{
return static_cast < Tdst > ( std : : max ( static_cast < Tsrc > ( 0 ) , src ) ) ;
} else
{
return static_cast < Tdst > ( std : : max ( static_cast < Tsrc > ( 0 ) , std : : min ( src , static_cast < Tsrc > ( std : : numeric_limits < Tdst > : : max ( ) ) ) ) ) ;
}
}
}
template < typename Tdst >
inline Tdst saturate_cast ( double src )
{
if ( src > = static_cast < double > ( std : : numeric_limits < Tdst > : : max ( ) ) )
{
return std : : numeric_limits < Tdst > : : max ( ) ;
}
if ( src < = static_cast < double > ( std : : numeric_limits < Tdst > : : min ( ) ) )
{
return std : : numeric_limits < Tdst > : : min ( ) ;
}
return static_cast < Tdst > ( src ) ;
}
template < typename Tdst >
inline Tdst saturate_cast ( float src )
{
if ( src > = static_cast < float > ( std : : numeric_limits < Tdst > : : max ( ) ) )
{
return std : : numeric_limits < Tdst > : : max ( ) ;
}
if ( src < = static_cast < float > ( std : : numeric_limits < Tdst > : : min ( ) ) )
{
return std : : numeric_limits < Tdst > : : min ( ) ;
}
return static_cast < Tdst > ( 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 <bit> header.
// Note that we do not use SFINAE here but instead rely on static_assert.
template < typename T >
MPT_CONSTEXPR14_FUN int popcount ( T val ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : value ) ;
int result = 0 ;
while ( val > 0 )
{
if ( val & 0x1 )
{
result + + ;
}
val > > = 1 ;
}
return result ;
}
template < typename T >
MPT_CONSTEXPR14_FUN bool has_single_bit ( T x ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : value ) ;
return mpt : : popcount ( x ) = = 1 ;
}
template < typename T >
MPT_CONSTEXPR14_FUN T bit_ceil ( T x ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : value ) ;
T result = 1 ;
while ( result < x )
{
T newresult = result < < 1 ;
if ( newresult < result )
{
return 0 ;
}
result = newresult ;
}
return result ;
}
template < typename T >
MPT_CONSTEXPR14_FUN T bit_floor ( T x ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : 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 < typename T >
MPT_CONSTEXPR14_FUN T bit_width ( T x ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : value ) ;
T result = 0 ;
while ( x > 0 )
{
x > > = 1 ;
result + = 1 ;
}
return result ;
}
namespace detail
{
template < typename T >
MPT_CONSTEXPR14_FUN T rotl ( T x , int r ) noexcept
{
auto N = std : : numeric_limits < T > : : digits ;
return ( x > > ( N - r ) ) | ( x < < r ) ;
}
template < typename T >
MPT_CONSTEXPR14_FUN T rotr ( T x , int r ) noexcept
{
auto N = std : : numeric_limits < T > : : digits ;
return ( x < < ( N - r ) ) | ( x > > r ) ;
}
} // namespace detail
template < typename T >
MPT_CONSTEXPR14_FUN T rotl ( T x , int s ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : value ) ;
auto N = std : : numeric_limits < T > : : digits ;
auto r = s % N ;
return ( s < 0 ) ? detail : : rotr ( x , - s ) : ( ( x > > ( N - r ) ) | ( x < < r ) ) ;
}
template < typename T >
MPT_CONSTEXPR14_FUN T rotr ( T x , int s ) noexcept
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : is_unsigned < T > : : value ) ;
auto N = std : : numeric_limits < T > : : 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 < typename Tmod , Tmod m >
struct ModIfNotZeroImpl
{
template < typename Tval >
inline Tval mod ( Tval x )
{
static_assert ( std : : numeric_limits < Tmod > : : is_integer ) ;
static_assert ( ! std : : numeric_limits < Tmod > : : is_signed ) ;
static_assert ( std : : numeric_limits < Tval > : : is_integer ) ;
static_assert ( ! std : : numeric_limits < Tval > : : is_signed ) ;
return static_cast < Tval > ( x % m ) ;
}
} ;
template < > struct ModIfNotZeroImpl < uint8 , 0 > { template < typename Tval > inline Tval mod ( Tval x ) { return x ; } } ;
template < > struct ModIfNotZeroImpl < uint16 , 0 > { template < typename Tval > inline Tval mod ( Tval x ) { return x ; } } ;
template < > struct ModIfNotZeroImpl < uint32 , 0 > { template < typename Tval > inline Tval mod ( Tval x ) { return x ; } } ;
template < > struct ModIfNotZeroImpl < uint64 , 0 > { template < typename Tval > 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 < typename Tmod , Tmod m , typename Tval >
inline Tval ModIfNotZero ( Tval x )
{
return detail : : ModIfNotZeroImpl < Tmod , m > ( ) . mod ( x ) ;
}
// Returns true iff Tdst can represent the value val.
// Use as if(Util::TypeCanHoldValue<uint8>(-1)).
template < typename Tdst , typename Tsrc >
inline bool TypeCanHoldValue ( Tsrc val )
{
return ( static_cast < Tsrc > ( mpt : : saturate_cast < Tdst > ( 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 < typename T , typename Tlimit >
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 < T > : : max ( ) - x ) ;
return std : : min ( x + add , mpt : : saturate_cast < T > ( limit ) ) ;
}
template < typename T >
inline T ExponentialGrow ( const T & x )
{
return Util : : ExponentialGrow ( x , std : : numeric_limits < T > : : max ( ) ) ;
}
} //namespace Util
2022-01-30 23:19:36 +00:00
namespace mpt
{
template < typename T >
inline T sanitize_nan ( T val )
{
static_assert ( std : : is_floating_point < T > : : value ) ;
if ( std : : isnan ( val ) )
{
return T ( 0.0 ) ;
}
return val ;
}
template < typename T >
inline T safe_clamp ( T v , T lo , T hi )
{
static_assert ( std : : is_floating_point < T > : : value ) ;
return std : : clamp ( mpt : : sanitize_nan ( v ) , lo , hi ) ;
}
} // namespace mpt
2021-12-26 11:29:43 +00:00
// 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 < class T , class C >
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 < class T , class C >
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 < typename T , typename C >
inline bool IsInRange ( T val , C lo , C hi )
{
return lo < = val & & val < = hi ;
}
// Like Limit, but with upperlimit only.
template < class T , class C >
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 < class T >
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 < typename T >
MPT_FORCEINLINE auto rshift_signed_standard ( T x , int y ) - > decltype ( x > > y )
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : numeric_limits < T > : : is_signed ) ;
typedef decltype ( x > > y ) result_type ;
typedef typename std : : make_unsigned < result_type > : : type unsigned_result_type ;
const unsigned_result_type roffset = static_cast < unsigned_result_type > ( 1 ) < < ( ( sizeof ( result_type ) * 8 ) - 1 ) ;
result_type rx = x ;
unsigned_result_type urx = static_cast < unsigned_result_type > ( rx ) ;
urx + = roffset ;
urx > > = y ;
urx - = roffset > > y ;
return static_cast < result_type > ( urx ) ;
}
template < typename T >
MPT_FORCEINLINE auto lshift_signed_standard ( T x , int y ) - > decltype ( x < < y )
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : numeric_limits < T > : : is_signed ) ;
typedef decltype ( x < < y ) result_type ;
typedef typename std : : make_unsigned < result_type > : : type unsigned_result_type ;
const unsigned_result_type roffset = static_cast < unsigned_result_type > ( 1 ) < < ( ( sizeof ( result_type ) * 8 ) - 1 ) ;
result_type rx = x ;
unsigned_result_type urx = static_cast < unsigned_result_type > ( rx ) ;
urx + = roffset ;
urx < < = y ;
urx - = roffset < < y ;
return static_cast < result_type > ( urx ) ;
}
# if MPT_COMPILER_SHIFT_SIGNED
template < typename T >
MPT_FORCEINLINE auto rshift_signed_undefined ( T x , int y ) - > decltype ( x > > y )
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : numeric_limits < T > : : is_signed ) ;
return x > > y ;
}
template < typename T >
MPT_FORCEINLINE auto lshift_signed_undefined ( T x , int y ) - > decltype ( x < < y )
{
static_assert ( std : : numeric_limits < T > : : is_integer ) ;
static_assert ( std : : numeric_limits < T > : : is_signed ) ;
return x < < y ;
}
template < typename T >
MPT_FORCEINLINE auto rshift_signed ( T x , int y ) - > decltype ( x > > y )
{
return mpt : : rshift_signed_undefined ( x , y ) ;
}
template < typename T >
MPT_FORCEINLINE auto lshift_signed ( T x , int y ) - > decltype ( x < < y )
{
return mpt : : lshift_signed_undefined ( x , y ) ;
}
# else
template < typename T >
MPT_FORCEINLINE auto rshift_signed ( T x , int y ) - > decltype ( x > > y )
{
return mpt : : rshift_signed_standard ( x , y ) ;
}
template < typename T >
MPT_FORCEINLINE auto lshift_signed ( T x , int y ) - > decltype ( x < < y )
{
return mpt : : lshift_signed_standard ( x , y ) ;
}
# endif
template < typename >
struct array_size ;
template < typename T , std : : size_t N >
struct array_size < std : : array < T , N > >
{
static constexpr std : : size_t size = N ;
} ;
template < typename T , std : : size_t N >
struct array_size < T [ N ] >
{
static constexpr std : : size_t size = N ;
} ;
} // namespace mpt
namespace Util
{
// Returns maximum value of given integer type.
template < class T > constexpr T MaxValueOfType ( const T & ) { static_assert ( std : : numeric_limits < T > : : is_integer = = true , " Only integer types are allowed. " ) ; return ( std : : numeric_limits < T > : : 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 < class T > inline T saturate_round ( double val )
{
static_assert ( std : : numeric_limits < T > : : is_integer = = true , " Type is a not an integer " ) ;
return mpt : : saturate_cast < T > ( mpt : : round ( val ) ) ;
}
template < class T > inline T saturate_round ( float val )
{
static_assert ( std : : numeric_limits < T > : : is_integer = = true , " Type is a not an integer " ) ;
return mpt : : saturate_cast < T > ( 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 < int64 > ( 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 < uint64 > ( a ) * b ;
# endif
}
MPT_FORCEINLINE int32 muldiv ( int32 a , int32 b , int32 c )
{
return mpt : : saturate_cast < int32 > ( mul32to64 ( a , b ) / c ) ;
}
MPT_FORCEINLINE int32 muldivr ( int32 a , int32 b , int32 c )
{
return mpt : : saturate_cast < int32 > ( ( 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 < uint32 > ( mul32to64_unsigned ( a , b ) / c ) ;
}
MPT_FORCEINLINE uint32 muldivr_unsigned ( uint32 a , uint32 b , uint32 c )
{
return mpt : : saturate_cast < uint32 > ( ( 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 < int32 > ( a / c ) : mpt : : saturate_cast < int32 > ( ( a - ( c - 1 ) ) / c ) ;
}
// rounds x up to multiples of target
template < typename T >
inline T AlignUp ( T x , T target )
{
return ( ( x + ( target - 1 ) ) / target ) * target ;
}
// rounds x down to multiples of target
template < typename T >
inline T AlignDown ( T x , T target )
{
return ( x / target ) * target ;
}
} // namespace Util
OPENMPT_NAMESPACE_END