[libcxx-commits] [libcxx] [libc++] to_chars/from_chars for _BitInt wider than 128 bits (proof of concept) (PR #204355)
Xavier Roche via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jun 17 06:57:20 PDT 2026
https://github.com/xroche created https://github.com/llvm/llvm-project/pull/204355
This is a proof of concept for discussion, not a request to merge.
Today `std::to_chars` and `std::from_chars` stop working once an integer is wider than 128 bits. A `_BitInt(256)` or `_BitInt(4096)` value cannot be written to text or read back from it: the conversion code only handles types up to 128 bits and rejects anything larger at compile time. This patch lifts that limit for `_BitInt`.
The interesting part is how little it takes. A `_BitInt` is already a full arbitrary-precision integer in the compiler, able to divide and take remainders at any width. Most of libc++'s conversion code already works at any width through those operations. Only the base-10 path took a shortcut that assumed a value fitting in 64 or 128 bits. The change sends wide values around that one shortcut and reuses everything else, so the implementation is a handful of lines.
Conformance status, worth stating plainly: this is a libc++ extension, not a standard feature. The current bit-precise-integer paper for the standard (P3666) steers the library away from supporting `_BitInt`, and `<charconv>` falls in the part it would leave out. So the compile error removed here is conforming behavior today, and nothing in this patch is proposed for the standard. The aim is to show with working, tested code that the support is small and correct, as one input to the ongoing discussion: https://discourse.llvm.org/t/implementing-p3666r4-bit-precise-integers-in-libc/91070
Scope. The tests cover both directions, every base from 2 to 36, signed and unsigned, the awkward cases (most-negative value, exact buffer limits, out-of-range input), and constant evaluation. The 4096-bit results are checked against decimal and hexadecimal strings generated independently by Python, so the tests are not merely self-consistent. Left out on purpose: `std::format` of wide `_BitInt`, and a faster algorithm for the largest widths. The current loop is simple and quadratic, so a value near the multi-million-bit maximum is slow and may exceed the compiler's compile-time step limit.
Assisted-by: Claude (Anthropic)
>From dfb85781a12b822c6feaa7fd04868da8f41c540c Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Wed, 17 Jun 2026 15:12:33 +0200
Subject: [PATCH 1/2] [libc++] Support to_chars/from_chars for _BitInt wider
than 128 bits
std::to_chars, std::from_chars (and the integer types they accept)
previously rejected any integer wider than 128 bits: the value was
promoted through __make_32_64_or_128_bit_t, which yields no type past
__int128, so a _BitInt(N > 128) was a hard compile error.
A _BitInt is a native arbitrary-precision integer; the compiler already
implements /, %, * and + on it. The base-2/8/16 paths, the generic
per-base loop, and the from_chars Horner accumulation already work on
any width through these operators. Only the base-10 fast paths route
through __itoa::__traits, which is specialized for 32/64/128-bit only.
Detect a wide _BitInt with __is_wide_bitint_v and route its base-10
conversion through the generic loop instead of the table-based path.
All other bases reuse the existing code unchanged.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../include/__charconv/from_chars_integral.h | 14 +++++--
libcxx/include/__charconv/to_chars_integral.h | 39 ++++++++++++-------
.../__type_traits/make_32_64_or_128_bit.h | 13 +++++++
3 files changed, 48 insertions(+), 18 deletions(-)
diff --git a/libcxx/include/__charconv/from_chars_integral.h b/libcxx/include/__charconv/from_chars_integral.h
index 3063c0978c8fa..c00fe5b6fce84 100644
--- a/libcxx/include/__charconv/from_chars_integral.h
+++ b/libcxx/include/__charconv/from_chars_integral.h
@@ -19,6 +19,7 @@
#include <__type_traits/is_integral.h>
#include <__type_traits/is_signed.h>
#include <__type_traits/is_unsigned.h>
+#include <__type_traits/make_32_64_or_128_bit.h>
#include <__type_traits/make_unsigned.h>
#include <limits>
@@ -171,8 +172,11 @@ inline constexpr float __from_chars_log2f_lut[35] = {
template <typename _Tp, __enable_if_t<is_unsigned<_Tp>::value, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI from_chars_result
__from_chars_integral(const char* __first, const char* __last, _Tp& __value, int __base) {
- if (__base == 10)
- return std::__from_chars_atoi(__first, __last, __value);
+ if (__base == 10) {
+ if constexpr (!__is_wide_bitint_v<_Tp>)
+ return std::__from_chars_atoi(__first, __last, __value);
+ // A wide _BitInt has no base-10 fast path; use the generic accumulator below.
+ }
return std::__subject_seq_combinator(
__first,
@@ -219,7 +223,11 @@ __from_chars_integral(const char* __first, const char* __last, _Tp& __value, int
template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI from_chars_result
from_chars(const char* __first, const char* __last, _Tp& __value) {
- return std::__from_chars_atoi(__first, __last, __value);
+ if constexpr (__is_wide_bitint_v<_Tp>)
+ // A wide _BitInt routes through the base-aware generic accumulator.
+ return std::__from_chars_integral(__first, __last, __value, 10);
+ else
+ return std::__from_chars_atoi(__first, __last, __value);
}
template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
diff --git a/libcxx/include/__charconv/to_chars_integral.h b/libcxx/include/__charconv/to_chars_integral.h
index 6d425139260b6..44e7fbd98a5cc 100644
--- a/libcxx/include/__charconv/to_chars_integral.h
+++ b/libcxx/include/__charconv/to_chars_integral.h
@@ -269,16 +269,19 @@ _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI int __to_chars_integral_widt
template <class _Tp, __enable_if_t<!is_signed<_Tp>::value, int> >
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI __to_chars_result
__to_chars_integral(char* __first, char* __last, _Tp __value, int __base) {
- if (__base == 10) [[likely]]
- return std::__to_chars_itoa(__first, __last, __value, false_type());
-
- switch (__base) {
- case 2:
- return std::__to_chars_integral<2>(__first, __last, __value);
- case 8:
- return std::__to_chars_integral<8>(__first, __last, __value);
- case 16:
- return std::__to_chars_integral<16>(__first, __last, __value);
+ if (__base == 10) [[likely]] {
+ if constexpr (!__is_wide_bitint_v<_Tp>)
+ return std::__to_chars_itoa(__first, __last, __value, false_type());
+ // A wide _BitInt has no base-10 fast path; fall through to the generic loop.
+ } else {
+ switch (__base) {
+ case 2:
+ return std::__to_chars_integral<2>(__first, __last, __value);
+ case 8:
+ return std::__to_chars_integral<8>(__first, __last, __value);
+ case 16:
+ return std::__to_chars_integral<16>(__first, __last, __value);
+ }
}
ptrdiff_t __cap = __last - __first;
@@ -321,9 +324,13 @@ to_chars_result to_chars(char*, char*, bool, int = 10) = delete;
template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI to_chars_result
to_chars(char* __first, char* __last, _Tp __value) {
- using _Type = __make_32_64_or_128_bit_t<_Tp>;
- static_assert(!is_same<_Type, void>::value, "unsupported integral type used in to_chars");
- return std::__to_chars_itoa(__first, __last, static_cast<_Type>(__value), is_signed<_Tp>());
+ if constexpr (__is_wide_bitint_v<_Tp>)
+ // A _BitInt wider than 128 bits has no native promotion target; the
+ // base-aware path converts it using the type's own arithmetic.
+ return std::__to_chars_integral(__first, __last, __value, 10);
+ else
+ return std::__to_chars_itoa(
+ __first, __last, static_cast<__make_32_64_or_128_bit_t<_Tp>>(__value), is_signed<_Tp>());
}
template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
@@ -331,8 +338,10 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI to_chars_result
to_chars(char* __first, char* __last, _Tp __value, int __base) {
_LIBCPP_ASSERT_UNCATEGORIZED(2 <= __base && __base <= 36, "base not in [2, 36]");
- using _Type = __make_32_64_or_128_bit_t<_Tp>;
- return std::__to_chars_integral(__first, __last, static_cast<_Type>(__value), __base);
+ if constexpr (__is_wide_bitint_v<_Tp>)
+ return std::__to_chars_integral(__first, __last, __value, __base);
+ else
+ return std::__to_chars_integral(__first, __last, static_cast<__make_32_64_or_128_bit_t<_Tp>>(__value), __base);
}
#endif // _LIBCPP_STD_VER >= 17
diff --git a/libcxx/include/__type_traits/make_32_64_or_128_bit.h b/libcxx/include/__type_traits/make_32_64_or_128_bit.h
index 7016209ec9c0a..e19517851b969 100644
--- a/libcxx/include/__type_traits/make_32_64_or_128_bit.h
+++ b/libcxx/include/__type_traits/make_32_64_or_128_bit.h
@@ -44,6 +44,19 @@ using __make_32_64_or_128_bit_t _LIBCPP_NODEBUG =
> > >;
// clang-format on
+// True for a _BitInt wider than 128 bits, i.e. an integer with no 32/64/128-bit
+// promotion target. charconv converts these with a bignum path built on the
+// type's own arithmetic. __make_32_64_or_128_bit_t is not usable to detect this:
+// for such a type it is not void but ill-formed (it feeds void to __make_unsigned),
+// so the test is on sizeof, matching the trait's own logic.
+template <class _Tp>
+inline const bool __is_wide_bitint_v =
+#if _LIBCPP_HAS_INT128
+ sizeof(_Tp) > sizeof(__int128_t);
+#else
+ sizeof(_Tp) > sizeof(long long);
+#endif
+
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___TYPE_TRAITS_MAKE_32_64_OR_128_BIT_H
>From 1f1ed50c03527fdd9e5c3d97f6bf41216229f28c Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Wed, 17 Jun 2026 15:24:57 +0200
Subject: [PATCH 2/2] [libc++][test] Cover to_chars/from_chars for _BitInt
wider than 128 bits
Extend the existing charconv integral tests with a _BitInt(N > 128)
section, guarded on __BITINT_MAXWIDTH__. The native harness does not
apply to wide _BitInt (its buffer is fixed-size and its reference is
strtoll), so the wide cases use hand-verified literals plus round-trip.
Coverage: signed min via the unsigned-counterpart magnitude, signed and
unsigned max, exact value_too_large boundaries, lowercase digits with no
base prefix (0x and 0b both rejected), from_chars out-of-range with value
left unmodified and ptr past the maximal match, the most-negative success
asymmetry, and a round-trip sweep over bases 2..36 and several widths.
The headline 4096-bit width runs in constant evaluation too, both on a
focused base/value subset and against an independent oracle: a sparse
4096-bit value whose decimal and hex strings are computed by Python's
arbitrary-precision int, which catches a bug to_chars and from_chars
might share that a round-trip cannot. Non-byte-aligned widths run at
runtime only, pending the clang-23 fix for constexpr
__builtin_mul_overflow on those widths (llvm.org/PR204085).
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../charconv.from.chars/integral.pass.cpp | 86 +++++++++++
.../integral.roundtrip.pass.cpp | 141 ++++++++++++++++++
.../charconv.to.chars/integral.pass.cpp | 65 +++++++-
3 files changed, 291 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp
index 4708da4c38b9b..4f653db802a25 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp
@@ -137,6 +137,85 @@ struct test_signed
}
};
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+// _BitInt wider than 128 bits parses via the generic Horner accumulator. The
+// error paths must leave value unmodified; check that with a sentinel.
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void fc_ok(const char (&s)[N], T expect, std::size_t consumed, int base = 10) {
+ T v = 0x55;
+ auto r = std::from_chars(s, s + N - 1, v, base);
+ assert(r.ec == std::errc{});
+ assert(r.ptr == s + consumed);
+ assert(v == expect);
+}
+
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void fc_invalid(const char (&s)[N], int base = 10) {
+ T v = 0x55;
+ auto r = std::from_chars(s, s + N - 1, v, base);
+ assert(r.ec == std::errc::invalid_argument);
+ assert(r.ptr == s); // no characters match -> ptr == first
+ assert(v == T(0x55)); // value unmodified
+}
+
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void fc_oor(const char (&s)[N], std::size_t consumed, int base = 10) {
+ T v = 0x55;
+ auto r = std::from_chars(s, s + N - 1, v, base);
+ assert(r.ec == std::errc::result_out_of_range);
+ assert(r.ptr == s + consumed); // past the maximal matching sequence
+ assert(v == T(0x55)); // value unmodified
+}
+
+TEST_CONSTEXPR_CXX23 bool test_wide() {
+ using S = signed _BitInt(256);
+ using U = unsigned _BitInt(256);
+
+ // Success and ptr placement.
+ fc_ok<U>("42", U(42), 2);
+ fc_ok<S>("-42", S(-42), 3);
+ fc_ok<S>("-0", S(0), 2);
+ fc_ok<U>("007", U(7), 3); // leading zeros consumed
+ fc_ok<U>("ff", U(255), 2, 16);
+ fc_ok<U>("FF", U(255), 2, 16); // case-insensitive
+ fc_ok<U>("123abc", U(123), 3); // base 10: stops at 'a'
+ fc_ok<U>("123abc", U(0x123abc), 6, 16);
+ fc_ok<U>("0x10", U(0), 1, 16); // no 0x prefix: matches '0', stops at 'x'
+ fc_ok<U>("0b10", U(0), 1, 2); // no 0b prefix: matches '0', stops at 'b'
+ fc_ok<U>("12", U(1), 1, 2); // '2' >= base 2: stops
+
+ // No match: value unmodified, ptr == first.
+ fc_invalid<U>("");
+ fc_invalid<U>("+5"); // '+' is never accepted
+ fc_invalid<S>("+5");
+ fc_invalid<U>("-5"); // '-' only for signed types
+ fc_invalid<S>("-"); // sign with no digits
+ fc_invalid<U>(" 5"); // leading whitespace is not skipped
+ fc_invalid<U>("z");
+
+ // Out of range: value unmodified, ptr past every matching digit.
+ fc_oor<U>("115792089237316195423570985008687907853269984665640564039457584007913129639936", 78); // 2^256
+ fc_oor<U>("115792089237316195423570985008687907853269984665640564039457584007913129639936zzz", 78);
+ fc_oor<S>("57896044618658097711785492504343953926634992332820282019728792003956564819968",
+ 77); // 2^255 = signed max + 1
+
+ // Most-negative -2^255 succeeds: magnitude is signed max + 1 but valid as a
+ // negative value.
+ fc_ok<S>("-57896044618658097711785492504343953926634992332820282019728792003956564819968",
+ std::numeric_limits<S>::min(),
+ 78);
+
+ // Exact boundaries: max accepted, max + 1 rejected.
+ fc_ok<U>("115792089237316195423570985008687907853269984665640564039457584007913129639935",
+ std::numeric_limits<U>::max(),
+ 78); // 2^256 - 1
+ fc_ok<S>("57896044618658097711785492504343953926634992332820282019728792003956564819967",
+ std::numeric_limits<S>::max(),
+ 77); // 2^255 - 1
+ return true;
+}
+#endif
+
TEST_CONSTEXPR_CXX23 bool test()
{
run<test_basics>(integrals);
@@ -151,5 +230,12 @@ int main(int, char**) {
static_assert(test());
#endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+ test_wide();
+# if TEST_STD_VER > 20
+ static_assert(test_wide());
+# endif
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp
index b1f2c77b34f8a..a05adba5fa07b 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp
@@ -77,6 +77,135 @@ struct test_signed : roundtrip_test_base<T>
}
};
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+// Round-trip is the broad-coverage backbone for wide _BitInt: to_chars then
+// from_chars must recover the value and consume exactly what was written.
+template <int Bits>
+TEST_CONSTEXPR_CXX23 void rt_one(unsigned _BitInt(Bits) u, int base) {
+ char buf[Bits + 2]; // base 2 is the widest: up to Bits digits, plus a sign
+
+ std::to_chars_result r = std::to_chars(buf, buf + sizeof(buf), u, base);
+ assert(r.ec == std::errc{});
+ unsigned _BitInt(Bits) ub = 1;
+ std::from_chars_result f = std::from_chars(buf, r.ptr, ub, base);
+ assert(f.ec == std::errc{} && f.ptr == r.ptr && ub == u);
+
+ signed _BitInt(Bits) s = static_cast<signed _BitInt(Bits)>(u);
+ r = std::to_chars(buf, buf + sizeof(buf), s, base);
+ assert(r.ec == std::errc{});
+ signed _BitInt(Bits) sb = 1;
+ f = std::from_chars(buf, r.ptr, sb, base);
+ assert(f.ec == std::errc{} && f.ptr == r.ptr && sb == s);
+}
+
+template <int Bits>
+TEST_CONSTEXPR_CXX23 void rt_width() {
+ using U = unsigned _BitInt(Bits);
+ using S = signed _BitInt(Bits);
+ for (int b = 2; b <= 36; ++b) {
+ rt_one<Bits>(U(0), b);
+ rt_one<Bits>(U(1), b);
+ rt_one<Bits>(std::numeric_limits<U>::max(), b); // all ones
+ rt_one<Bits>(static_cast<U>(std::numeric_limits<S>::max()), b); // signed max
+ rt_one<Bits>(static_cast<U>(std::numeric_limits<S>::min()), b); // signed min pattern
+ rt_one<Bits>(std::numeric_limits<U>::max() / 3, b); // arbitrary spread
+ }
+}
+
+// Byte-aligned widths only, kept small enough for constant evaluation.
+TEST_CONSTEXPR_CXX23 bool test_wide() {
+ rt_width<256>();
+ return true;
+}
+
+// 4096 bits is the headline "reasonable width": exercise it in constant
+// evaluation too, on a representative base/value subset. The full base sweep at
+// this width is kept at runtime (test_wide_runtime) to bound constexpr cost.
+TEST_CONSTEXPR_CXX23 bool test_wide_4096() {
+ using U = unsigned _BitInt(4096);
+ using S = signed _BitInt(4096);
+ const U vals[] = {
+ U(0),
+ U(1),
+ std::numeric_limits<U>::max(), // all ones
+ static_cast<U>(std::numeric_limits<S>::min()), // signed min pattern
+ std::numeric_limits<U>::max() / 3, // arbitrary spread
+ };
+ for (U v : vals) {
+ rt_one<4096>(v, 2);
+ rt_one<4096>(v, 10);
+ rt_one<4096>(v, 16);
+ }
+ return true;
+}
+
+// Independent oracle for a huge 4096-bit value. The decimal and hex strings are
+// computed by Python's arbitrary-precision int, so they catch a bug that
+// to_chars and from_chars might share, which a pure round-trip cannot. The value
+// is built from its bits, independent of the strings; the sparse pattern gives
+// long interior-zero digit runs plus a few clusters.
+TEST_CONSTEXPR_CXX23 bool test_wide_oracle() {
+ using U = unsigned _BitInt(4096);
+ const U v = (U(1) << 4095) | (U(1) << 3000) | (U(1) << 1500) | (U(1) << 700) | U(0xDEADBEEFCAFEULL);
+
+ const char dec[] =
+ "522194440706576253345876355358312191289982124523691890192116741641976953985778728424413405967498779170445053357"
+ "219631418993786719092896803631618043925682638972978488271854999170180795067191859157214035005927973113188159419"
+ "698856372836167342172293308748403954352901852035642024370059304557233988891799014503343469488440893892973454045"
+ "327052631416966658275225011404027988935425249341701966051886267829647532452891421103226957550676830796693159988"
+ "124656750434275418911327793613319612182498160234866841889152188408311919545372889132335138169799010480395144069"
+ "285672659513941271465489792446864983055373122330734549035017206965872628152160774541392717285230405353948204585"
+ "721558937253615907178075626191061140618987126175926194937994062055639754879354953746504582267597824430780046407"
+ "654767442218179275002597906567544454462957002216329001106694021676565785487186595814555929076464240677469561177"
+ "107218357758641620381943183829806874856274226199817314163151233513560130957415780849310362681536620722609148912"
+ "812834526196870692951390912269511897170617673817942753906754404005592596448147882135379795095255338256287637038"
+ "130680736166406732152594023784824442544596004877042110465174074339660336326179460333127852406170867605207327911"
+ "022565509886";
+ const char hex[] =
+ "80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000deadbeefcafe";
+
+ char buf[4100];
+ std::to_chars_result r = std::to_chars(buf, buf + sizeof(buf), v, 10);
+ assert(r.ec == std::errc{} && static_cast<std::size_t>(r.ptr - buf) == sizeof(dec) - 1);
+ for (std::size_t i = 0; i + 1 < sizeof(dec); ++i)
+ assert(buf[i] == dec[i]);
+
+ r = std::to_chars(buf, buf + sizeof(buf), v, 16);
+ assert(r.ec == std::errc{} && static_cast<std::size_t>(r.ptr - buf) == sizeof(hex) - 1);
+ for (std::size_t i = 0; i + 1 < sizeof(hex); ++i)
+ assert(buf[i] == hex[i]);
+
+ U back = 0;
+ std::from_chars_result f = std::from_chars(dec, dec + sizeof(dec) - 1, back, 10);
+ assert(f.ec == std::errc{} && f.ptr == dec + sizeof(dec) - 1 && back == v);
+
+ back = 0;
+ f = std::from_chars(hex, hex + sizeof(hex) - 1, back, 16);
+ assert(f.ec == std::errc{} && f.ptr == hex + sizeof(hex) - 1 && back == v);
+ return true;
+}
+
+// Wider and non-byte-aligned widths at runtime. Non-aligned widths are excluded
+// from constant evaluation pending the clang-23 fix for constexpr
+// __builtin_mul_overflow on non-byte-aligned _BitInt (llvm.org/PR204085).
+void test_wide_runtime() {
+ rt_width<512>();
+ rt_width<4096>();
+ rt_width<129>();
+ rt_width<257>();
+ rt_width<1000>();
+}
+#endif
+
TEST_CONSTEXPR_CXX23 bool test()
{
run<test_basics>(integrals);
@@ -91,5 +220,17 @@ int main(int, char**) {
static_assert(test());
#endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+ test_wide();
+ test_wide_4096();
+ test_wide_oracle();
+ test_wide_runtime();
+# if TEST_STD_VER > 20
+ static_assert(test_wide());
+ static_assert(test_wide_4096());
+ static_assert(test_wide_oracle());
+# endif
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp
index e89b340bdfcbe..170a8ff5c5c35 100644
--- a/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp
@@ -293,6 +293,62 @@ struct test_signed : to_chars_test_base<T>
}
};
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+// _BitInt wider than 128 bits has no native promotion target, so the native
+// harness above (fixed buffer, strtoll reference) does not apply. Check it with
+// hand-verified literals plus exact-fit / one-too-short buffer handling.
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void test_wide_one(T v, const char (&expect)[N], int base = 10) {
+ const std::size_t len = N - 1;
+ char buf[4200];
+
+ std::to_chars_result r = std::to_chars(buf, buf + sizeof(buf), v, base);
+ assert(r.ec == std::errc{});
+ assert(static_cast<std::size_t>(r.ptr - buf) == len);
+ for (std::size_t i = 0; i < len; ++i)
+ assert(buf[i] == expect[i]);
+
+ // Exact-fit buffer succeeds; one byte short fails with ptr == last and no
+ // assertion on the (unspecified) buffer contents.
+ r = std::to_chars(buf, buf + len, v, base);
+ assert(r.ec == std::errc{} && r.ptr == buf + len);
+ r = std::to_chars(buf, buf + len - 1, v, base);
+ assert(r.ec == std::errc::value_too_large && r.ptr == buf + len - 1);
+}
+
+TEST_CONSTEXPR_CXX23 bool test_wide() {
+ using S = signed _BitInt(256);
+ using U = unsigned _BitInt(256);
+ using SLn = std::numeric_limits<S>;
+ using ULn = std::numeric_limits<U>;
+
+ test_wide_one(U(0), "0");
+ test_wide_one(S(0), "0", 16);
+ test_wide_one(U(255), "255");
+ test_wide_one(U(255), "ff", 16); // lowercase, no 0x prefix
+ test_wide_one(U(0xabcu), "abc", 16);
+ test_wide_one(U(10), "1010", 2);
+ test_wide_one(S(-12), "-12");
+ test_wide_one(S(-255), "-ff", 16);
+ test_wide_one(S(-10), "-1010", 2);
+
+ // Trailing/interior zeros must survive (no stray digit dropping).
+ test_wide_one(U(1000000000u), "1000000000");
+
+ // Signed min = -2^255: magnitude 2^255 is not representable in S, so it must
+ // be formed in the unsigned counterpart. (Base 2 is covered by the round-trip
+ // test to avoid a 257-character literal.)
+ test_wide_one(SLn::min(), "-57896044618658097711785492504343953926634992332820282019728792003956564819968");
+ test_wide_one(SLn::min(), "-8000000000000000000000000000000000000000000000000000000000000000", 16);
+
+ // Signed max = 2^255-1, unsigned max = 2^256-1.
+ test_wide_one(SLn::max(), "57896044618658097711785492504343953926634992332820282019728792003956564819967");
+ test_wide_one(ULn::max(), "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
+
+ return true;
+}
+#endif
+
TEST_CONSTEXPR_CXX23 bool test()
{
run<test_basics>(integrals);
@@ -308,5 +364,12 @@ int main(int, char**)
static_assert(test());
#endif
- return 0;
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+ test_wide();
+# if TEST_STD_VER > 20
+ static_assert(test_wide());
+# endif
+#endif
+
+ return 0;
}
More information about the libcxx-commits
mailing list