[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:
Louis Dionne 2019-09-04 12:48:32 +00:00
parent b9e9478244
commit e8316372b9
2 changed files with 124 additions and 0 deletions

View File

@ -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

View File

@ -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>();
}