You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
5.4 KiB
146 lines
5.4 KiB
8 months ago
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
#ifndef UTIL_SATURATE_CAST_H_
|
||
|
#define UTIL_SATURATE_CAST_H_
|
||
|
|
||
|
#include <cmath>
|
||
|
#include <limits>
|
||
|
#include <type_traits>
|
||
|
|
||
|
namespace openscreen {
|
||
|
|
||
|
// Case 0: When To and From are the same type, saturate_cast<> is pass-through.
|
||
|
template <typename To, typename From>
|
||
|
constexpr std::enable_if_t<
|
||
|
std::is_same<std::remove_cv<To>, std::remove_cv<From>>::value,
|
||
|
To>
|
||
|
saturate_cast(From from) {
|
||
|
return from;
|
||
|
}
|
||
|
|
||
|
// Because of the way C++ signed versus unsigned comparison works (i.e., the
|
||
|
// type promotion strategy employed), extra care must be taken to range-check
|
||
|
// the input value. For example, if the current architecture is 32-bits, then
|
||
|
// any int32_t compared with a uint32_t will NOT promote to a int64_t↔int64_t
|
||
|
// comparison. Instead, it will become a uint32_t↔uint32_t comparison (!),
|
||
|
// which will sometimes produce invalid results.
|
||
|
|
||
|
// Case 1: "From" and "To" are either both signed, or are both unsigned. In
|
||
|
// this case, the smaller of the two types will be promoted to match the
|
||
|
// larger's size, and a valid comparison will be made.
|
||
|
template <typename To, typename From>
|
||
|
constexpr std::enable_if_t<
|
||
|
std::is_integral<From>::value && std::is_integral<To>::value &&
|
||
|
(std::is_signed<From>::value == std::is_signed<To>::value),
|
||
|
To>
|
||
|
saturate_cast(From from) {
|
||
|
if (from <= std::numeric_limits<To>::min()) {
|
||
|
return std::numeric_limits<To>::min();
|
||
|
}
|
||
|
if (from >= std::numeric_limits<To>::max()) {
|
||
|
return std::numeric_limits<To>::max();
|
||
|
}
|
||
|
return static_cast<To>(from);
|
||
|
}
|
||
|
|
||
|
// Case 2: "From" is signed, but "To" is unsigned.
|
||
|
template <typename To, typename From>
|
||
|
constexpr std::enable_if_t<
|
||
|
std::is_integral<From>::value && std::is_integral<To>::value &&
|
||
|
std::is_signed<From>::value && !std::is_signed<To>::value,
|
||
|
To>
|
||
|
saturate_cast(From from) {
|
||
|
if (from <= From{0}) {
|
||
|
return To{0};
|
||
|
}
|
||
|
if (static_cast<std::make_unsigned_t<From>>(from) >=
|
||
|
std::numeric_limits<To>::max()) {
|
||
|
return std::numeric_limits<To>::max();
|
||
|
}
|
||
|
return static_cast<To>(from);
|
||
|
}
|
||
|
|
||
|
// Case 3: "From" is unsigned, but "To" is signed.
|
||
|
template <typename To, typename From>
|
||
|
constexpr std::enable_if_t<
|
||
|
std::is_integral<From>::value && std::is_integral<To>::value &&
|
||
|
!std::is_signed<From>::value && std::is_signed<To>::value,
|
||
|
To>
|
||
|
saturate_cast(From from) {
|
||
|
if (from >= static_cast<typename std::make_unsigned_t<To>>(
|
||
|
std::numeric_limits<To>::max())) {
|
||
|
return std::numeric_limits<To>::max();
|
||
|
}
|
||
|
return static_cast<To>(from);
|
||
|
}
|
||
|
|
||
|
// Case 4: "From" is a floating-point type, and "To" is an integer type (signed
|
||
|
// or unsigned). The result is truncated, per the usual C++ float-to-int
|
||
|
// conversion rules.
|
||
|
template <typename To, typename From>
|
||
|
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
|
||
|
std::is_integral<To>::value,
|
||
|
To>
|
||
|
saturate_cast(From from) {
|
||
|
// Note: It's invalid to compare the argument against
|
||
|
// std::numeric_limits<To>::max() because the latter, an integer value, will
|
||
|
// be type-promoted to the floating-point type. The problem is that the
|
||
|
// conversion is imprecise, as "max int" might not be exactly representable as
|
||
|
// a floating-point value (depending on the actual types of From and To).
|
||
|
//
|
||
|
// Thus, the strategy is to compare only floating-point values/constants to
|
||
|
// determine whether the bounds of the range of integers has been exceeded.
|
||
|
// Two assumptions here: 1) "To" is either unsigned, or is a 2's complement
|
||
|
// signed integer type. 2) "From" is a floating-point type that can exactly
|
||
|
// represent all powers of 2 within its value range.
|
||
|
static_assert((~To(1) + To(1)) == To(-1), "assumed 2's complement integers");
|
||
|
constexpr From kMaxIntPlusOne =
|
||
|
From(To(1) << (std::numeric_limits<To>::digits - 1)) * From(2);
|
||
|
constexpr From kMaxInt = kMaxIntPlusOne - 1;
|
||
|
// Note: In some cases, the kMaxInt constant will equal kMaxIntPlusOne because
|
||
|
// there isn't an exact floating-point representation for 2^N - 1. That said,
|
||
|
// the following upper-bound comparison is still valid because all
|
||
|
// floating-point values less than 2^N would also be less than 2^N - 1.
|
||
|
if (from >= kMaxInt) {
|
||
|
return std::numeric_limits<To>::max();
|
||
|
}
|
||
|
if (std::is_signed<To>::value) {
|
||
|
constexpr From kMinInt = -kMaxIntPlusOne;
|
||
|
if (from <= kMinInt) {
|
||
|
return std::numeric_limits<To>::min();
|
||
|
}
|
||
|
} else /* if To is unsigned */ {
|
||
|
if (from <= From(0)) {
|
||
|
return To(0);
|
||
|
}
|
||
|
}
|
||
|
return static_cast<To>(from);
|
||
|
}
|
||
|
|
||
|
// Like saturate_cast<>, but rounds to the nearest integer instead of
|
||
|
// truncating.
|
||
|
template <typename To, typename From>
|
||
|
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
|
||
|
std::is_integral<To>::value,
|
||
|
To>
|
||
|
rounded_saturate_cast(From from) {
|
||
|
const To saturated = saturate_cast<To>(from);
|
||
|
if (saturated == std::numeric_limits<To>::min() ||
|
||
|
saturated == std::numeric_limits<To>::max()) {
|
||
|
return saturated;
|
||
|
}
|
||
|
|
||
|
static_assert(sizeof(To) <= sizeof(decltype(llround(from))),
|
||
|
"No version of lround() for the required range of values.");
|
||
|
if (sizeof(To) <= sizeof(decltype(lround(from)))) {
|
||
|
return static_cast<To>(lround(from));
|
||
|
}
|
||
|
return static_cast<To>(llround(from));
|
||
|
}
|
||
|
|
||
|
} // namespace openscreen
|
||
|
|
||
|
#endif // UTIL_SATURATE_CAST_H_
|