forked from OSchip/llvm-project
[libc++] Add `__truncating_cast` for safely casting float types to integers
This is needed anytime we need to clamp an arbitrary floating point value to an integer type. Thanks to Eric Fiselier for the patch. Differential Revision: https://reviews.llvm.org/D66836 llvm-svn: 370891
This commit is contained in:
parent
b9e9478244
commit
e8316372b9
|
@ -1553,6 +1553,40 @@ inline _LIBCPP_INLINE_VISIBILITY
|
|||
typename std::enable_if<std::is_integral<_A1>::value, double>::type
|
||||
trunc(_A1 __lcpp_x) _NOEXCEPT {return ::trunc((double)__lcpp_x);}
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_STD
|
||||
|
||||
template <class _IntT, class _FloatT,
|
||||
bool _FloatBigger = (numeric_limits<_FloatT>::digits > numeric_limits<_IntT>::digits),
|
||||
int _Bits = (numeric_limits<_IntT>::digits - numeric_limits<_FloatT>::digits)>
|
||||
_LIBCPP_INLINE_VISIBILITY
|
||||
_LIBCPP_CONSTEXPR _IntT __max_representable_int_for_float() _NOEXCEPT {
|
||||
static_assert(is_floating_point<_FloatT>::value, "must be a floating point type");
|
||||
static_assert(is_integral<_IntT>::value, "must be an integral type");
|
||||
static_assert(numeric_limits<_FloatT>::radix == 2, "FloatT has incorrect radix");
|
||||
static_assert(_IsSame<_FloatT, float>::value || _IsSame<_FloatT, double>::value
|
||||
|| _IsSame<_FloatT,long double>::value, "unsupported floating point type");
|
||||
return _FloatBigger ? numeric_limits<_IntT>::max() : (numeric_limits<_IntT>::max() >> _Bits << _Bits);
|
||||
}
|
||||
|
||||
// Convert a floating point number to the specified integral type after
|
||||
// clamping to the integral types representable range.
|
||||
//
|
||||
// The behavior is undefined if `__r` is NaN.
|
||||
template <class _IntT, class _RealT>
|
||||
_LIBCPP_INLINE_VISIBILITY
|
||||
_IntT __clamp_to_integral(_RealT __r) _NOEXCEPT {
|
||||
using _Lim = std::numeric_limits<_IntT>;
|
||||
const _IntT _MaxVal = std::__max_representable_int_for_float<_IntT, _RealT>();
|
||||
if (__r >= ::nextafter(static_cast<_RealT>(_MaxVal), INFINITY)) {
|
||||
return _Lim::max();
|
||||
} else if (__r <= _Lim::lowest()) {
|
||||
return _Lim::min();
|
||||
}
|
||||
return static_cast<_IntT>(__r);
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_STD
|
||||
|
||||
} // extern "C++"
|
||||
|
||||
#endif // __cplusplus
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// __clamp_to_integral<IntT>(RealT)
|
||||
|
||||
// Test the conversion function that truncates floating point types to the
|
||||
// closest representable value for the specified integer type, or
|
||||
// numeric_limits<IntT>::max()/min() if the value isn't representable.
|
||||
|
||||
#include <limits>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
template <class IntT>
|
||||
void test() {
|
||||
typedef std::numeric_limits<IntT> Lim;
|
||||
const bool MaxIsRepresentable = sizeof(IntT) < 8;
|
||||
const bool IsSigned = std::is_signed<IntT>::value;
|
||||
struct TestCase {
|
||||
double Input;
|
||||
IntT Expect;
|
||||
bool IsRepresentable;
|
||||
} TestCases[] = {
|
||||
{0, 0, true},
|
||||
{1, 1, true},
|
||||
{IsSigned ? static_cast<IntT>(-1) : 0,
|
||||
IsSigned ? static_cast<IntT>(-1) : 0, true},
|
||||
{Lim::lowest(), Lim::lowest(), true},
|
||||
{static_cast<double>(Lim::max()), Lim::max(), MaxIsRepresentable},
|
||||
{static_cast<double>(Lim::max()) + 1, Lim::max(), false},
|
||||
{static_cast<double>(Lim::max()) + 1024, Lim::max(), false},
|
||||
{nextafter(static_cast<double>(Lim::max()), INFINITY), Lim::max(), false},
|
||||
};
|
||||
for (TestCase TC : TestCases) {
|
||||
auto res = std::__clamp_to_integral<IntT>(TC.Input);
|
||||
assert(res == TC.Expect);
|
||||
if (TC.IsRepresentable) {
|
||||
auto other = static_cast<IntT>(std::trunc(TC.Input));
|
||||
assert(res == other);
|
||||
} else
|
||||
assert(res == Lim::min() || res == Lim::max());
|
||||
}
|
||||
}
|
||||
|
||||
template <class IntT>
|
||||
void test_float() {
|
||||
typedef std::numeric_limits<IntT> Lim;
|
||||
const bool MaxIsRepresentable = sizeof(IntT) < 4;
|
||||
((void)MaxIsRepresentable);
|
||||
const bool IsSigned = std::is_signed<IntT>::value;
|
||||
struct TestCase {
|
||||
float Input;
|
||||
IntT Expect;
|
||||
bool IsRepresentable;
|
||||
} TestCases[] = {
|
||||
{0, 0, true},
|
||||
{1, 1, true},
|
||||
{IsSigned ? static_cast<IntT>(-1) : 0,
|
||||
IsSigned ? static_cast<IntT>(-1) : 0, true},
|
||||
{Lim::lowest(), Lim::lowest(), true},
|
||||
{static_cast<float>(Lim::max()), Lim::max(), MaxIsRepresentable },
|
||||
{nextafter(static_cast<float>(Lim::max()), INFINITY), Lim::max(), false},
|
||||
};
|
||||
for (TestCase TC : TestCases) {
|
||||
auto res = std::__clamp_to_integral<IntT>(TC.Input);
|
||||
assert(res == TC.Expect);
|
||||
if (TC.IsRepresentable) {
|
||||
auto other = static_cast<IntT>(std::trunc(TC.Input));
|
||||
assert(res == other);
|
||||
} else
|
||||
assert(res == Lim::min() || res == Lim::max());
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
test<short>();
|
||||
test<unsigned short>();
|
||||
test<int>();
|
||||
test<unsigned>();
|
||||
test<long long>();
|
||||
test<unsigned long long>();
|
||||
test_float<short>();
|
||||
test_float<int>();
|
||||
test_float<long long>();
|
||||
}
|
Loading…
Reference in New Issue