Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion doc/modules/ROOT/pages/literals.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,30 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str, st

The macros at the end allow you to write out a 128-bit number like you would with say `UINT64_C` without having to add quotes.

NOTE: The minimum `int128_t` value cannot be written as a negated `_i128` literal, because its positive magnitude (2^127) exceeds the maximum `int128_t` value and so fails to parse (yielding `0`). Use `BOOST_INT128_INT128_C(-170141183460469231731687303715884105728)` or `(std::numeric_limits<int128_t>::min)()` instead.
== Bases

The literals accept the standard C++ integer base prefixes in addition to plain decimal.
A `0x` or `0X` prefix selects hexadecimal, `0b` or `0B` selects binary, and a leading `0` selects octal.
The prefix is stripped and the remaining digits are parsed in the selected base.
Digit separators (`'`) may be used in any base.

[source, c++]
----
0xDEAD'BEEF_u128 // hexadecimal, == 3735928559
0b1111'0000_u128 // binary, == 240
0777_u128 // octal, == 511
255_u128 // decimal, == 255
-0xFF_i128 // == -255 (the unary minus is applied after parsing)
"-0b1010"_i128 // == -10 (the string form may embed the sign)
----

== Error Handling

An invalid literal is rejected at compile time rather than being silently accepted.
This includes a value that overflows the target type, a digit that is not valid for the selected base (for example `0b2` or `09`), and an empty magnitude such as `0x`.
When such a literal is used in a constant expression the parse reaches `BOOST_INT128_UNREACHABLE`, which is not a constant expression and therefore is a hard compile error.

NOTE: The minimum `int128_t` value cannot be written as a negated `_i128` literal, because its positive magnitude (2^127) exceeds the maximum `int128_t` value and so fails to parse (a hard compile error in a constant expression). Use `BOOST_INT128_INT128_C(-170141183460469231731687303715884105728)`, the string form `"-170141183460469231731687303715884105728"_i128`, or `(std::numeric_limits<int128_t>::min)()` instead.

See the xref:examples.adoc#examples_construction[construction examples] for usage demonstrations of both literals and macros.

Expand Down
8 changes: 4 additions & 4 deletions examples/to_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ int main()
std::cout << "Match: " << (s_large_str == s_large_std) << std::endl;

// Values beyond 64-bit range.
// The positive magnitude 2^127 exceeds INT128_MAX, so INT128_MIN cannot be
// written as a negated _i128 literal; this line therefore yields 0. Use the
// INT128_C macro (below) or std::numeric_limits<int128_t>::min() instead.
const auto large_negative {-170141183460469231731687303715884105728_i128};
// INT128_MIN via a negated numeric literal fails: the sign is not part of the
// literal, so the positive magnitude 2^127 is read first and rejected as out of
// range. A string literal keeps the sign with the digits, so it parses cleanly.
const auto large_negative {"-170141183460469231731687303715884105728"_i128};
std::cout << "\nint128_t min with string literal: " << to_string(large_negative) << std::endl;

const auto large_negative_c {BOOST_INT128_INT128_C(-170141183460469231731687303715884105728)};
Expand Down
119 changes: 119 additions & 0 deletions include/boost/int128/detail/mini_from_chars.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#include <limits>
#include <cstddef>

#if !(defined(BOOST_INT128_HAS_GPU_SUPPORT) || defined(BOOST_INT128_DISABLE_EXCEPTIONS))
#include <stdexcept>
#endif

#endif

namespace boost {
Expand Down Expand Up @@ -265,8 +269,123 @@ BOOST_INT128_TEST_EXPORT BOOST_INT128_HOST_DEVICE constexpr int from_chars_liter
return impl::from_chars_integer_impl<int128_t, uint128_t, true>(first, last, value, base);
}

// Rejects an out of range literal
[[noreturn]] BOOST_INT128_HOST_DEVICE inline void parse_literal_out_of_range()
{
#if defined(BOOST_INT128_HAS_GPU_SUPPORT) || defined(BOOST_INT128_DISABLE_EXCEPTIONS)
BOOST_INT128_UNREACHABLE;
#else
BOOST_INT128_THROW_EXCEPTION(std::out_of_range("Literal is out of range of the target type"));
#endif
}

// Rejects an invalid literal
[[noreturn]] BOOST_INT128_HOST_DEVICE inline void parse_invalid_literal()
{
#if defined(BOOST_INT128_HAS_GPU_SUPPORT) || defined(BOOST_INT128_DISABLE_EXCEPTIONS)
BOOST_INT128_UNREACHABLE;
#else
BOOST_INT128_THROW_EXCEPTION(std::invalid_argument("Literal is not a valid integer"));
#endif
}

// GCC before 6 rejects a constexpr function that contains a throw-expression or a
// call to a non-constexpr function anywhere in its body so we need to use unreachable in that case
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6
# define BOOST_INT128_REJECT_LITERAL(reporter) BOOST_INT128_UNREACHABLE
#else
# define BOOST_INT128_REJECT_LITERAL(reporter) reporter()
#endif

// Parse a user-defined literal, hard-failing on any malformed or out-of-range input.
// A C++ base prefix (0x/0X hex, 0b/0B binary, or a leading 0 for octal) is stripped and
// the digits parsed in that base, otherwise handled as base 10
template <typename Integer>
BOOST_INT128_HOST_DEVICE constexpr Integer parse_literal(const char* first, const char* last) noexcept
{
Integer value {};

// A leading sign stays with the digits; a base prefix, if present, follows it.
auto next = first;
const bool negative {next != last && *next == '-'};
if (negative)
{
++next;
}

int base {10};
bool prefixed {false};

if (last - next >= 2 && *next == '0')
{
const char marker {next[1]};
if (marker == 'x' || marker == 'X')
{
base = 16;
next += 2;
prefixed = true;
}
else if (marker == 'b' || marker == 'B')
{
base = 2;
next += 2;
prefixed = true;
}
else
{
base = 8;
next += 1;
prefixed = true;
}
}

// With no prefix, from_chars_literal handles the sign and the full decimal range.
// Overflow is reported as EDOM; anything else short of full consumption is malformed.
if (!prefixed)
{
const auto status = from_chars_literal(first, last, value);
if (status == EDOM)
{
BOOST_INT128_REJECT_LITERAL(parse_literal_out_of_range);
}
else if (status != first - last)
{
BOOST_INT128_REJECT_LITERAL(parse_invalid_literal);
}

return value;
}

// Prefixed: parse the magnitude in the detected base, then reapply the sign.
const auto status = from_chars_literal(next, last, value, base);
if (status == EDOM)
{
BOOST_INT128_REJECT_LITERAL(parse_literal_out_of_range);
}
else if (status != next - last)
{
BOOST_INT128_REJECT_LITERAL(parse_invalid_literal);
}

if (negative)
{
BOOST_INT128_IF_CONSTEXPR (std::numeric_limits<Integer>::is_signed)
{
value = static_cast<Integer>(-value);
}
else
{
BOOST_INT128_UNREACHABLE;
}
}

return value;
}

} // namespace detail
} // namespace int128
} // namespace boost

#undef BOOST_INT128_REJECT_LITERAL

#endif //MINI_FROM_CHARS_HPP
32 changes: 8 additions & 24 deletions include/boost/int128/literals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,58 +17,42 @@ namespace literals {

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_u128(const char* str) noexcept
{
uint128_t result {};
detail::from_chars_literal(str, str + detail::strlen(str), result);
return result;
return detail::parse_literal<uint128_t>(str, str + detail::strlen(str));
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_U128(const char* str) noexcept
{
uint128_t result {};
detail::from_chars_literal(str, str + detail::strlen(str), result);
return result;
return detail::parse_literal<uint128_t>(str, str + detail::strlen(str));
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_u128(const char* str, std::size_t len) noexcept
{
uint128_t result {};
detail::from_chars_literal(str, str + len, result);
return result;
return detail::parse_literal<uint128_t>(str, str + len);
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator ""_U128(const char* str, std::size_t len) noexcept
{
uint128_t result {};
detail::from_chars_literal(str, str + len, result);
return result;
return detail::parse_literal<uint128_t>(str, str + len);
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_i128(const char* str) noexcept
{
int128_t result {};
detail::from_chars_literal(str, str + detail::strlen(str), result);
return result;
return detail::parse_literal<int128_t>(str, str + detail::strlen(str));
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str) noexcept
{
int128_t result {};
detail::from_chars_literal(str, str + detail::strlen(str), result);
return result;
return detail::parse_literal<int128_t>(str, str + detail::strlen(str));
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_i128(const char* str, std::size_t len) noexcept
{
int128_t result {};
detail::from_chars_literal(str, str + len, result);
return result;
return detail::parse_literal<int128_t>(str, str + len);
}

BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str, std::size_t len) noexcept
{
int128_t result {};
detail::from_chars_literal(str, str + len, result);
return result;
return detail::parse_literal<int128_t>(str, str + len);
}

} // namespace literals
Expand Down
4 changes: 4 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,7 @@ compile compile_tests/numeric_compile.cpp ;
compile compile_tests/string_compile.cpp ;
compile compile_tests/random_compile.cpp ;
compile compile_tests/utilities_compile.cpp ;

compile-fail compile_tests/literals_overflow_fail.cpp ;
compile-fail compile_tests/literals_base_prefix_fail.cpp ;
compile-fail compile_tests/literals_invalid_fail.cpp ;
19 changes: 19 additions & 0 deletions test/compile_tests/literals_base_prefix_fail.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// A prefixed user-defined literal whose value does not fit the target type must be
// rejected at compile time. 0x1 followed by 32 zeros is 2^128, one past the
// uint128_t maximum, so the base-16 parse overflows and this must not compile.

#include <boost/int128/literals.hpp>

using namespace boost::int128::literals;

int main()
{
constexpr auto value = 0x100000000000000000000000000000000_u128;
static_cast<void>(value);

return 0;
}
19 changes: 19 additions & 0 deletions test/compile_tests/literals_invalid_fail.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// A user-defined literal whose characters are not valid digits for the base must be
// rejected at compile time instead of being silently truncated. "12xyz" parses the
// leading 12 and then hits the invalid 'x', so the literal is malformed.

#include <boost/int128/literals.hpp>

using namespace boost::int128::literals;

int main()
{
constexpr auto value = "12xyz"_u128;
static_cast<void>(value);

return 0;
}
18 changes: 18 additions & 0 deletions test/compile_tests/literals_overflow_fail.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// A user-defined literal whose value does not fit the target type must be rejected
// at compile time instead of being silently accepted.

#include <boost/int128/literals.hpp>

using namespace boost::int128::literals;

int main()
{
constexpr auto value = 340282366920938463463374607431768211456_u128;
static_cast<void>(value);

return 0;
}
Loading
Loading