summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Dionne <ldionne@apple.com>2019-09-04 12:48:32 +0000
committerDan Albert <danalbert@google.com>2019-09-13 14:46:25 -0700
commit55b6136b3a5fae060687b580a90a131623debd5a (patch)
tree148edc5ec596b76bec870b03be7ebfedbe4f38e6
parenta93d177f03e3f2baaff5f7d7696dd78090f00b48 (diff)
downloadlibcxx-55b6136b3a5fae060687b580a90a131623debd5a.tar.gz
DO NOT MERGE: [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 git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@370891 91177308-0d34-0410-b5e6-96231b3b80d8 (cherry picked from commit c9ac8d533010d8915bcfdecab891fb451f71ce74) (cherry picked from commit 4561f55204960c0b3bc4594089ddcf56e5655cad) (cherry picked from commit f4471784fdb5799b9862febc385a26becfbe53b3) Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=994957 Bug: http://b/139690488 Change-Id: I3d402ed0237a2f5f934a5d368ca91304bfcd9ed0
-rw-r--r--include/math.h34
-rw-r--r--test/libcxx/numerics/clamp_to_integral.pass.cpp90
2 files changed, 124 insertions, 0 deletions
diff --git a/include/math.h b/include/math.h
index b7659267e..b61c2253d 100644
--- a/include/math.h
+++ b/include/math.h
@@ -1426,6 +1426,40 @@ trunc(_A1 __lcpp_x) _NOEXCEPT {return ::trunc((double)__lcpp_x);}
#endif // !(defined(_LIBCPP_MSVCRT) && ((_VC_CRT_MAJOR_VERSION-0) < 14))
+_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
diff --git a/test/libcxx/numerics/clamp_to_integral.pass.cpp b/test/libcxx/numerics/clamp_to_integral.pass.cpp
new file mode 100644
index 000000000..cb3336f52
--- /dev/null
+++ b/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<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>();
+}