From e8316372b91e182ba9c7b4bae3bf2ddeff1065cc Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 4 Sep 2019 12:48:32 +0000 Subject: [PATCH] [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 --- libcxx/include/math.h | 34 ++++++++ .../libcxx/numerics/clamp_to_integral.pass.cpp | 90 ++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 libcxx/test/libcxx/numerics/clamp_to_integral.pass.cpp diff --git a/libcxx/include/math.h b/libcxx/include/math.h index 194df20..9294be6 100644 --- a/libcxx/include/math.h +++ b/libcxx/include/math.h @@ -1553,6 +1553,40 @@ inline _LIBCPP_INLINE_VISIBILITY typename std::enable_if::value, double>::type trunc(_A1 __lcpp_x) _NOEXCEPT {return ::trunc((double)__lcpp_x);} +_LIBCPP_BEGIN_NAMESPACE_STD + +template ::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 +_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 diff --git a/libcxx/test/libcxx/numerics/clamp_to_integral.pass.cpp b/libcxx/test/libcxx/numerics/clamp_to_integral.pass.cpp new file mode 100644 index 0000000..cb3336f --- /dev/null +++ b/libcxx/test/libcxx/numerics/clamp_to_integral.pass.cpp @@ -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(RealT) + +// Test the conversion function that truncates floating point types to the +// closest representable value for the specified integer type, or +// numeric_limits::max()/min() if the value isn't representable. + +#include +#include +#include + +template +void test() { + typedef std::numeric_limits Lim; + const bool MaxIsRepresentable = sizeof(IntT) < 8; + const bool IsSigned = std::is_signed::value; + struct TestCase { + double Input; + IntT Expect; + bool IsRepresentable; + } TestCases[] = { + {0, 0, true}, + {1, 1, true}, + {IsSigned ? static_cast(-1) : 0, + IsSigned ? static_cast(-1) : 0, true}, + {Lim::lowest(), Lim::lowest(), true}, + {static_cast(Lim::max()), Lim::max(), MaxIsRepresentable}, + {static_cast(Lim::max()) + 1, Lim::max(), false}, + {static_cast(Lim::max()) + 1024, Lim::max(), false}, + {nextafter(static_cast(Lim::max()), INFINITY), Lim::max(), false}, + }; + for (TestCase TC : TestCases) { + auto res = std::__clamp_to_integral(TC.Input); + assert(res == TC.Expect); + if (TC.IsRepresentable) { + auto other = static_cast(std::trunc(TC.Input)); + assert(res == other); + } else + assert(res == Lim::min() || res == Lim::max()); + } +} + +template +void test_float() { + typedef std::numeric_limits Lim; + const bool MaxIsRepresentable = sizeof(IntT) < 4; + ((void)MaxIsRepresentable); + const bool IsSigned = std::is_signed::value; + struct TestCase { + float Input; + IntT Expect; + bool IsRepresentable; + } TestCases[] = { + {0, 0, true}, + {1, 1, true}, + {IsSigned ? static_cast(-1) : 0, + IsSigned ? static_cast(-1) : 0, true}, + {Lim::lowest(), Lim::lowest(), true}, + {static_cast(Lim::max()), Lim::max(), MaxIsRepresentable }, + {nextafter(static_cast(Lim::max()), INFINITY), Lim::max(), false}, + }; + for (TestCase TC : TestCases) { + auto res = std::__clamp_to_integral(TC.Input); + assert(res == TC.Expect); + if (TC.IsRepresentable) { + auto other = static_cast(std::trunc(TC.Input)); + assert(res == other); + } else + assert(res == Lim::min() || res == Lim::max()); + } +} + +int main() { + test(); + test(); + test(); + test(); + test(); + test(); + test_float(); + test_float(); + test_float(); +} -- 2.7.4