mirror of https://github.com/OpenTTD/OpenTTD
Codechange: introduce generic ClampTo function to clamp to the range of a type
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…
Reference in New Issue