Skip to content

Commit

Permalink
[libc++] Add __truncating_cast for safely casting float types to in…
Browse files Browse the repository at this point in the history
…tegers

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

git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@370891 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
ldionne committed Sep 4, 2019
1 parent 40f365f commit c9ac8d5
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
34 changes: 34 additions & 0 deletions include/math.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions test/libcxx/numerics/clamp_to_integral.pass.cpp
Original file line number Diff line number Diff line change
@@ -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>();
}

0 comments on commit c9ac8d5

Please sign in to comment.