[libcxx-commits] [libcxx] [libc++] std::abs support for _BitInt(N) and __int128 (PR #196532)

Xavier Roche via libcxx-commits libcxx-commits at lists.llvm.org
Fri May 8 06:36:22 PDT 2026


https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/196532

>From c3fa4b590aeb9e038653b7bf5e909cf69b3610be Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 8 May 2026 15:05:42 +0200
Subject: [PATCH 1/2] [libc++] Add std::abs support for _BitInt(N) and __int128

Add a constrained template overload of std::abs in __math/abs.h that
covers signed integer types not handled by the existing builtin
overloads for int, long, and long long. The new template matches any
signed integer type with sizeof(_Tp) >= sizeof(int), so __int128_t
and signed _BitInt(N >= 32) work, while shorter standard types still
go through abs(int) via integer promotion.

The implementation is x < 0 ? -x : x, the same conditional negation
the standard uses to define abs for signed integers.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 libcxx/include/__math/abs.h                   | 15 ++++
 .../numerics/c.math/abs.bitint.pass.cpp       | 82 +++++++++++++++++++
 2 files changed, 97 insertions(+)
 create mode 100644 libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp

diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index b780159f11ebf..5610d5685ed77 100644
--- a/libcxx/include/__math/abs.h
+++ b/libcxx/include/__math/abs.h
@@ -11,7 +11,9 @@
 
 #include <__config>
 #include <__type_traits/enable_if.h>
+#include <__type_traits/integer_traits.h>
 #include <__type_traits/is_integral.h>
+#include <__type_traits/is_same.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -63,6 +65,19 @@ template <class = int>
   return __builtin_llabs(__x);
 }
 
+// Overload for __int128 and signed _BitInt(N >= 32) types. The sizeof
+// check excludes shorter signed types (e.g. signed short, signed
+// _BitInt(16)). The standard overloads catch the standard types via
+// integer promotion. _BitInt narrower than int does not promote and
+// is intentionally unsupported by std::abs.
+template <class _Tp,
+          __enable_if_t<__is_signed_integer_v<_Tp> && !is_same<_Tp, int>::value && !is_same<_Tp, long>::value &&
+                            !is_same<_Tp, long long>::value && (sizeof(_Tp) >= sizeof(int)),
+                        int> = 0>
+[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _Tp abs(_Tp __x) _NOEXCEPT {
+  return __x < 0 ? -__x : __x;
+}
+
 } // namespace __math
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
new file mode 100644
index 0000000000000..be0b77de8701b
--- /dev/null
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// std::abs for _BitInt(N) and __int128 -- libc++ extension over the standard
+// abs(int / long / long long) overloads. The new template covers signed
+// integer types not handled by the existing builtin overloads, gated on
+// __is_signed_integer_v plus a sizeof(_Tp) >= sizeof(int) check that keeps
+// shorter standard types on the integer-promotion path.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <cassert>
+#include <cmath>
+
+#include "test_macros.h"
+
+#if TEST_HAS_EXTENSION(bit_int)
+template <int N>
+void test_signed_bitint() {
+  using T = signed _BitInt(N);
+  assert(std::abs(T(0)) == T(0));
+  assert(std::abs(T(1)) == T(1));
+  assert(std::abs(T(42)) == T(42));
+  assert(std::abs(T(-1)) == T(1));
+  assert(std::abs(T(-42)) == T(42));
+}
+#endif
+
+int main(int, char**) {
+#if TEST_HAS_EXTENSION(bit_int)
+  // _BitInt(N) with N < sizeof(int) * CHAR_BIT does not match the new
+  // template (sizeof guard) and does not implicit-promote to int either, so
+  // it has no abs overload. Start at 32 bits.
+  test_signed_bitint<32>();
+  test_signed_bitint<64>();
+
+  // Odd widths >= 32 bits.
+  test_signed_bitint<33>();
+  test_signed_bitint<63>();
+  test_signed_bitint<65>();
+
+#  if __BITINT_MAXWIDTH__ >= 128
+  test_signed_bitint<128>();
+#  endif
+
+#  if __BITINT_MAXWIDTH__ >= 256
+  test_signed_bitint<129>();
+  test_signed_bitint<256>();
+
+  // Large value: |-2^200| == 2^200. Python: abs(-(1 << 200)) == 1 << 200.
+  signed _BitInt(256) v        = -(static_cast<signed _BitInt(256)>(1) << 200);
+  signed _BitInt(256) expected = static_cast<signed _BitInt(256)>(1) << 200;
+  assert(std::abs(v) == expected);
+#  endif
+
+#  if __BITINT_MAXWIDTH__ >= 1024
+  test_signed_bitint<512>();
+  test_signed_bitint<1024>();
+#  endif
+#endif // TEST_HAS_EXTENSION(bit_int)
+
+#if _LIBCPP_HAS_INT128
+  assert(std::abs(static_cast<__int128_t>(0)) == 0);
+  assert(std::abs(static_cast<__int128_t>(42)) == 42);
+  assert(std::abs(static_cast<__int128_t>(-42)) == 42);
+  assert(std::abs(static_cast<__int128_t>(-1)) == 1);
+
+  // INT128_MIN is unrepresentable as a positive __int128; -__x overflows.
+  // Skip that case -- the standard's abs(int) has the same issue with
+  // INT_MIN, so this is consistent.
+  __int128_t big_neg = -((static_cast<__int128_t>(1) << 100));
+  __int128_t big_pos = static_cast<__int128_t>(1) << 100;
+  assert(std::abs(big_neg) == big_pos);
+#endif
+
+  return 0;
+}

>From 2483bd6071500b8516e9563e5190d16cf828ec67 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 8 May 2026 15:23:33 +0200
Subject: [PATCH 2/2] [libc++][test] Cover boundary values in std::abs _BitInt
 test

Add T_MAX (identity) and T_MIN + 1 (cleanly negates to T_MAX) to the
per-width signed _BitInt test. The T_MIN case is intentionally not
tested -- abs(T_MIN) overflows in signed arithmetic, which is
undefined behaviour, matching the standard's abs(int) caveat for
INT_MIN.

Same boundary added to the __int128_t section for INT128_MAX and
INT128_MIN + 1.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 .../numerics/c.math/abs.bitint.pass.cpp       | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
index be0b77de8701b..05bfac0a32f76 100644
--- a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -15,7 +15,7 @@
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
 #include <cassert>
-#include <cmath>
+#include <cstdlib>
 
 #include "test_macros.h"
 
@@ -28,6 +28,16 @@ void test_signed_bitint() {
   assert(std::abs(T(42)) == T(42));
   assert(std::abs(T(-1)) == T(1));
   assert(std::abs(T(-42)) == T(42));
+
+  // Boundary cases. T_MAX has no overflow (identity). T_MIN + 1 negates to
+  // T_MAX, which is the largest negative value abs handles cleanly. T_MIN
+  // itself is intentionally not tested: -T_MIN overflows in signed
+  // arithmetic, which is undefined behaviour. The same caveat applies to
+  // std::abs(int) with INT_MIN.
+  T t_max       = static_cast<T>(~static_cast<unsigned _BitInt(N)>(0) >> 1);
+  T t_min_plus1 = -t_max; // == T_MIN + 1
+  assert(std::abs(t_max) == t_max);
+  assert(std::abs(t_min_plus1) == t_max);
 }
 #endif
 
@@ -76,6 +86,13 @@ int main(int, char**) {
   __int128_t big_neg = -((static_cast<__int128_t>(1) << 100));
   __int128_t big_pos = static_cast<__int128_t>(1) << 100;
   assert(std::abs(big_neg) == big_pos);
+
+  // Boundary: INT128_MAX (identity) and INT128_MIN+1 (negation cleanly
+  // produces INT128_MAX).
+  __int128_t int128_max       = static_cast<__int128_t>(~static_cast<__uint128_t>(0) >> 1);
+  __int128_t int128_min_plus1 = -int128_max;
+  assert(std::abs(int128_max) == int128_max);
+  assert(std::abs(int128_min_plus1) == int128_max);
 #endif
 
   return 0;



More information about the libcxx-commits mailing list