diff --git a/src/core/math_func.hpp b/src/core/math_func.hpp index 8f9b5626c9..d11ef1ddd3 100644 --- a/src/core/math_func.hpp +++ b/src/core/math_func.hpp @@ -10,6 +10,9 @@ #ifndef MATH_FUNC_HPP #define MATH_FUNC_HPP +#include +#include + /** * Returns the absolute value of (scalar) variable. * @@ -124,6 +127,68 @@ static inline uint ClampU(const uint a, const uint min, const uint max) return Clamp(a, min, max); } +/** + * Clamp the given value down to lie within the requested type. + * + * For example ClampTo will return a value clamped to the range of 0 + * to 255. Anything smaller will become 0, anything larger will become 255. + * + * @param a The 64-bit value to clamp. + * @return The 64-bit value reduced to a value within the given allowed range + * for the return type. + * @see Clamp(int, int, int) + */ +template +constexpr To ClampTo(From value) +{ + static_assert(std::numeric_limits::is_integer, "Do not clamp from non-integer values"); + static_assert(std::numeric_limits::is_integer, "Do not clamp to non-integer values"); + + if (sizeof(To) >= sizeof(From) && std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + /* Same signedness and To type is larger or equal than From type, no clamping is required. */ + return static_cast(value); + } + + if (sizeof(To) > sizeof(From) && std::numeric_limits::is_signed) { + /* Signed destination and a larger To type, no clamping is required. */ + return static_cast(value); + } + + /* Get the bigger of the two types based on essentially the number of bits. */ + using BiggerType = typename std::conditional= sizeof(To), From, To>::type; + + if constexpr (std::numeric_limits::is_signed) { + /* The output is a signed number. */ + if constexpr (std::numeric_limits::is_signed) { + /* Both input and output are signed. */ + return static_cast(std::clamp(value, + std::numeric_limits::lowest(), std::numeric_limits::max())); + } + + /* The input is unsigned, so skip the minimum check and use unsigned variant of the biggest type as intermediate type. */ + using BiggerUnsignedType = typename std::make_unsigned::type; + return static_cast(std::min(std::numeric_limits::max(), value)); + } + + /* The output is unsigned. */ + + if constexpr (std::numeric_limits::is_signed) { + /* Input is signed; account for the negative numbers in the input. */ + if constexpr (sizeof(To) >= sizeof(From)) { + /* If the output type is larger or equal to the input type, then only clamp the negative numbers. */ + return static_cast(std::max(value, 0)); + } + + /* The output type is smaller than the input type. */ + using BiggerSignedType = typename std::make_signed::type; + return static_cast(std::clamp(value, + std::numeric_limits::lowest(), std::numeric_limits::max())); + } + + /* The input and output are unsigned, just clamp at the high side. */ + return static_cast(std::min(value, std::numeric_limits::max())); +} + /** * Reduce a signed 64-bit int to a signed 32-bit one * @@ -140,7 +205,7 @@ static inline uint ClampU(const uint a, const uint min, const uint max) */ static inline int32 ClampToI32(const int64 a) { - return static_cast(Clamp(a, INT32_MIN, INT32_MAX)); + return ClampTo(a); } /** @@ -152,11 +217,7 @@ static inline int32 ClampToI32(const int64 a) */ static inline uint16 ClampToU16(const uint64 a) { - /* MSVC thinks, in its infinite wisdom, that int min(int, int) is a better - * match for min(uint64, uint) than uint64 min(uint64, uint64). As such we - * need to cast the UINT16_MAX to prevent MSVC from displaying its - * infinite loads of warnings. */ - return static_cast(std::min(a, static_cast(UINT16_MAX))); + return ClampTo(a); } /** diff --git a/src/core/overflowsafe_type.hpp b/src/core/overflowsafe_type.hpp index 0c3957aaad..37eab29023 100644 --- a/src/core/overflowsafe_type.hpp +++ b/src/core/overflowsafe_type.hpp @@ -215,6 +215,10 @@ static_assert(OverflowSafeInt32(INT32_MAX) + 1 == OverflowSafeInt32(INT32_MAX)); static_assert(OverflowSafeInt32(INT32_MAX) * 2 == OverflowSafeInt32(INT32_MAX)); static_assert(OverflowSafeInt32(INT32_MIN) * 2 == OverflowSafeInt32(INT32_MIN)); +/* Specialisation of the generic ClampTo function for overflow safe integers to normal integers. */ +template +constexpr To ClampTo(OverflowSafeInt value) { return ClampTo(From(value)); } + #undef HAS_OVERFLOW_BUILTINS #endif /* OVERFLOWSAFE_TYPE_HPP */ diff --git a/src/tests/math_func.cpp b/src/tests/math_func.cpp index 6cc43f0146..8db813c105 100644 --- a/src/tests/math_func.cpp +++ b/src/tests/math_func.cpp @@ -75,3 +75,48 @@ TEST_CASE("IntSqrtTest - FindSqRt") CHECK(9 == IntSqrt(88)); CHECK(1696 == IntSqrt(2876278)); } + + +TEST_CASE("ClampTo") +{ + CHECK(0 == ClampTo(std::numeric_limits::lowest())); + CHECK(0 == ClampTo(-1)); + CHECK(0 == ClampTo(0)); + CHECK(1 == ClampTo(1)); + + CHECK(255 == ClampTo(std::numeric_limits::max())); + CHECK(255 == ClampTo(256)); + CHECK(255 == ClampTo(255)); + CHECK(254 == ClampTo(254)); + + CHECK(-128 == ClampTo(std::numeric_limits::lowest())); + CHECK(-128 == ClampTo(-129)); + CHECK(-128 == ClampTo(-128)); + CHECK(-127 == ClampTo(-127)); + + CHECK(127 == ClampTo(std::numeric_limits::max())); + CHECK(127 == ClampTo(128)); + CHECK(127 == ClampTo(127)); + CHECK(126 == ClampTo(126)); + + CHECK(126 == ClampTo(static_cast(126))); + CHECK(126 == ClampTo(static_cast(126))); + CHECK(0 == ClampTo(static_cast(-126))); + CHECK(0 == ClampTo(static_cast(-126))); + + /* The realm around 64 bits types is tricky as there is not one type/method that works for all. */ + + /* lowest/max uint64_t does not get clamped when clamping to uint64_t. */ + CHECK(std::numeric_limits::lowest() == ClampTo(std::numeric_limits::lowest())); + CHECK(std::numeric_limits::max() == ClampTo(std::numeric_limits::max())); + + /* negative int64_t get clamped to 0. */ + CHECK(0 == ClampTo(std::numeric_limits::lowest())); + CHECK(0 == ClampTo(int64_t(-1))); + /* positive int64_t remain the same. */ + CHECK(1 == ClampTo(int64_t(1))); + CHECK(static_cast(std::numeric_limits::max()) == ClampTo(std::numeric_limits::max())); + + /* max uint64_t gets clamped to max int64_t. */ + CHECK(std::numeric_limits::max() == ClampTo(std::numeric_limits::max())); +}