Codechange: introduce generic ClampTo function to clamp to the range of a type

pull/532/head
Rubidium 1 year ago committed by rubidium42
parent e33b2afd87
commit 969a3dc0f3

@ -10,6 +10,9 @@
#ifndef MATH_FUNC_HPP
#define MATH_FUNC_HPP
#include <limits>
#include <type_traits>
/**
* 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<uint>(a, min, max);
}
/**
* Clamp the given value down to lie within the requested type.
*
* For example ClampTo<uint8_t> 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 <typename To, typename From>
constexpr To ClampTo(From value)
{
static_assert(std::numeric_limits<To>::is_integer, "Do not clamp from non-integer values");
static_assert(std::numeric_limits<From>::is_integer, "Do not clamp to non-integer values");
if (sizeof(To) >= sizeof(From) && std::numeric_limits<To>::is_signed == std::numeric_limits<From>::is_signed) {
/* Same signedness and To type is larger or equal than From type, no clamping is required. */
return static_cast<To>(value);
}
if (sizeof(To) > sizeof(From) && std::numeric_limits<To>::is_signed) {
/* Signed destination and a larger To type, no clamping is required. */
return static_cast<To>(value);
}
/* Get the bigger of the two types based on essentially the number of bits. */
using BiggerType = typename std::conditional<sizeof(From) >= sizeof(To), From, To>::type;
if constexpr (std::numeric_limits<To>::is_signed) {
/* The output is a signed number. */
if constexpr (std::numeric_limits<From>::is_signed) {
/* Both input and output are signed. */
return static_cast<To>(std::clamp<BiggerType>(value,
std::numeric_limits<To>::lowest(), std::numeric_limits<To>::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<BiggerType>::type;
return static_cast<To>(std::min<BiggerUnsignedType>(std::numeric_limits<To>::max(), value));
}
/* The output is unsigned. */
if constexpr (std::numeric_limits<From>::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<To>(std::max<From>(value, 0));
}
/* The output type is smaller than the input type. */
using BiggerSignedType = typename std::make_signed<BiggerType>::type;
return static_cast<To>(std::clamp<BiggerSignedType>(value,
std::numeric_limits<To>::lowest(), std::numeric_limits<To>::max()));
}
/* The input and output are unsigned, just clamp at the high side. */
return static_cast<To>(std::min<BiggerType>(value, std::numeric_limits<To>::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<int32>(Clamp<int64>(a, INT32_MIN, INT32_MAX));
return ClampTo<int32>(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<uint16>(std::min(a, static_cast<uint64>(UINT16_MAX)));
return ClampTo<uint16>(a);
}
/**

@ -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 <typename To, typename From>
constexpr To ClampTo(OverflowSafeInt<From> value) { return ClampTo<To>(From(value)); }
#undef HAS_OVERFLOW_BUILTINS
#endif /* OVERFLOWSAFE_TYPE_HPP */

@ -75,3 +75,48 @@ TEST_CASE("IntSqrtTest - FindSqRt")
CHECK(9 == IntSqrt(88));
CHECK(1696 == IntSqrt(2876278));
}
TEST_CASE("ClampTo")
{
CHECK(0 == ClampTo<uint8_t>(std::numeric_limits<int64_t>::lowest()));
CHECK(0 == ClampTo<uint8_t>(-1));
CHECK(0 == ClampTo<uint8_t>(0));
CHECK(1 == ClampTo<uint8_t>(1));
CHECK(255 == ClampTo<uint8_t>(std::numeric_limits<uint64_t>::max()));
CHECK(255 == ClampTo<uint8_t>(256));
CHECK(255 == ClampTo<uint8_t>(255));
CHECK(254 == ClampTo<uint8_t>(254));
CHECK(-128 == ClampTo<int8_t>(std::numeric_limits<int64_t>::lowest()));
CHECK(-128 == ClampTo<int8_t>(-129));
CHECK(-128 == ClampTo<int8_t>(-128));
CHECK(-127 == ClampTo<int8_t>(-127));
CHECK(127 == ClampTo<int8_t>(std::numeric_limits<uint64_t>::max()));
CHECK(127 == ClampTo<int8_t>(128));
CHECK(127 == ClampTo<int8_t>(127));
CHECK(126 == ClampTo<int8_t>(126));
CHECK(126 == ClampTo<int64_t>(static_cast<uint8>(126)));
CHECK(126 == ClampTo<uint64_t>(static_cast<int8>(126)));
CHECK(0 == ClampTo<uint64_t>(static_cast<int8>(-126)));
CHECK(0 == ClampTo<uint8_t>(static_cast<int8>(-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<uint64_t>::lowest() == ClampTo<uint64_t>(std::numeric_limits<uint64_t>::lowest()));
CHECK(std::numeric_limits<uint64_t>::max() == ClampTo<uint64_t>(std::numeric_limits<uint64_t>::max()));
/* negative int64_t get clamped to 0. */
CHECK(0 == ClampTo<uint64_t>(std::numeric_limits<int64_t>::lowest()));
CHECK(0 == ClampTo<uint64_t>(int64_t(-1)));
/* positive int64_t remain the same. */
CHECK(1 == ClampTo<uint64_t>(int64_t(1)));
CHECK(static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) == ClampTo<uint64_t>(std::numeric_limits<int64_t>::max()));
/* max uint64_t gets clamped to max int64_t. */
CHECK(std::numeric_limits<int64_t>::max() == ClampTo<int64_t>(std::numeric_limits<uint64_t>::max()));
}

Loading…
Cancel
Save