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

via libcxx-commits libcxx-commits at lists.llvm.org
Fri May 8 13:54:17 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Xavier Roche (xroche)

<details>
<summary>Changes</summary>

Add a constrained template overload of std::abs 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 using `x < 0 ? -x : x`.

Shorter standard types (signed char, signed short) still go through
`abs(int)` via integer promotion. `_BitInt(N)` narrower than `int`
does not promote and is intentionally unsupported.

Part of the [_BitInt(N) libc++ effort](https://discourse.llvm.org/t/bitint-n-support-in-libc-investigations-possible-improvements-looking-for-guidance/90063).

Assisted-by: Claude (Anthropic)

---
Full diff: https://github.com/llvm/llvm-project/pull/196532.diff


2 Files Affected:

- (modified) libcxx/include/__math/abs.h (+20) 
- (added) libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp (+101) 


``````````diff
diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index b780159f11ebf..4181173f6d82f 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,24 @@ template <class = int>
   return __builtin_llabs(__x);
 }
 
+// Overload for __int128 and signed _BitInt(N) where sizeof(_Tp) >=
+// sizeof(int). The sizeof check excludes shorter signed types (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.
+//
+// The explicit !is_same exclusions for int/long/long long are
+// load-bearing: signed _BitInt(33..64) shares sizeof with long long, so
+// a sizeof-only gate would let those types compete with the existing
+// builtin-based overloads.
+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..11f4f121450b7
--- /dev/null
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <cstdlib>
+
+#include "test_macros.h"
+
+#if TEST_HAS_EXTENSION(bit_int)
+template <int N>
+void test_signed_bitint() {
+  using T = signed _BitInt(N);
+  ASSERT_SAME_TYPE(decltype(std::abs(T(0))), T);
+  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));
+
+  // 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
+
+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_SAME_TYPE(decltype(std::abs(static_cast<__int128_t>(0))), __int128_t);
+  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);
+
+  // 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;
+}

``````````

</details>


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


More information about the libcxx-commits mailing list