diff --git a/doc/modules/ROOT/pages/literals.adoc b/doc/modules/ROOT/pages/literals.adoc index 4e63b200..b03f4c6c 100644 --- a/doc/modules/ROOT/pages/literals.adoc +++ b/doc/modules/ROOT/pages/literals.adoc @@ -10,13 +10,17 @@ https://www.boost.org/LICENSE_1_0.txt == Description -The library provides user-defined literal suffixes for concise construction of safe integer types. +The library provides user-defined literal suffixes for concise construction of safe numeric types. The literals are defined in the `boost::safe_numbers::literals` namespace. -The behavior of the literals depends on the type: +The behavior of each literal depends on the target type: - For `_u8`, `_u16`, and `_u32`, the literal value is range-checked and throws `std::overflow_error` if the value exceeds the target type's maximum. - The `_u64` literal performs no range check since `unsigned long long` maps directly to `std::uint64_t` (statically verified). -- The `_u128` literal parses a string representation and throws `std::overflow_error` on overflow or `std::invalid_argument` on invalid input. +- The `_u128` literal accepts an integer-form token, parses the digit string, and throws `std::overflow_error` on overflow or `std::invalid_argument` on invalid input. +- For `_i8`, `_i16`, `_i32`, and `_i64`, the literal magnitude is range-checked against the signed maximum and throws `std::overflow_error` if it exceeds it. + Negative values are formed by applying the wrapper's unary `operator-` to the positive literal: the expression `-42_i32` parses as `-(42_i32)`. +- The `_i128` literal mirrors `_u128`: it parses an integer-form digit string into `int128_t` and throws on overflow or invalid input. +- For `_f32` and `_f64`, the literal is taken as `long double` and range-checked against the target's maximum, throwing `std::overflow_error` on overflow. [source,c++] ---- @@ -30,6 +34,15 @@ constexpr auto operator ""_u32(unsigned long long int val) -> u32; constexpr auto operator ""_u64(unsigned long long int val) noexcept -> u64; constexpr auto operator ""_u128(const char* str) -> u128; +constexpr auto operator ""_i8(unsigned long long int val) -> i8; +constexpr auto operator ""_i16(unsigned long long int val) -> i16; +constexpr auto operator ""_i32(unsigned long long int val) -> i32; +constexpr auto operator ""_i64(unsigned long long int val) -> i64; +constexpr auto operator ""_i128(const char* str) -> i128; + +constexpr auto operator ""_f32(long double val) -> f32; +constexpr auto operator ""_f64(long double val) -> f64; + } // namespace boost::safe_numbers::literals ---- @@ -56,7 +69,35 @@ constexpr auto operator ""_u128(const char* str) -> u128; | `_u128` | `u128` -| Parses string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input +| Parses digit string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input + +| `_i8` +| `i8` +| Throws `std::overflow_error` if magnitude > 127 + +| `_i16` +| `i16` +| Throws `std::overflow_error` if magnitude > 32,767 + +| `_i32` +| `i32` +| Throws `std::overflow_error` if magnitude > 2,147,483,647 + +| `_i64` +| `i64` +| Throws `std::overflow_error` if magnitude > 9,223,372,036,854,775,807 + +| `_i128` +| `i128` +| Parses digit string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input + +| `_f32` +| `f32` +| Throws `std::overflow_error` if value > `std::numeric_limits::max()` + +| `_f64` +| `f64` +| Throws `std::overflow_error` if value > `std::numeric_limits::max()` |=== == Usage @@ -72,14 +113,37 @@ constexpr auto b {1000_u16}; constexpr auto c {100000_u32}; constexpr auto d {9999999999_u64}; constexpr auto e {340282366920938463463374607431768211455_u128}; + +constexpr auto f {-42_i8}; +constexpr auto g {32767_i16}; +constexpr auto h {-2147483647_i32}; +constexpr auto i {9223372036854775807_i64}; +constexpr auto j {-170141183460469231731687303715884105727_i128}; + +constexpr auto k {3.14_f32}; +constexpr auto l {2.718281828459045_f64}; ---- Literals are `constexpr` and can be used in compile-time contexts. When used in a `constexpr` context, an out-of-range value produces a compile error rather than a runtime exception. +== Negative Signed Literals + +C++ user-defined literal operators never see a leading minus sign as part of their input. +The expression `-42_i32` is parsed as `-(42_i32)`: the literal returns a positive `i32` and unary `operator-` on the wrapper produces the negative result. +Because the literal's range check is against the positive maximum (`INT_MAX`), the asymmetric `INT_MIN` value is not directly expressible through the literal: writing `-128_i8` parses as `-(128_i8)`, and the inner literal throws because `128 > INT8_MAX`. +Use direct construction for that one value: + +[source,c++] +---- +constexpr auto min_i8 {i8{std::numeric_limits::min()}}; +---- + +The same asymmetry applies to `_i16`, `_i32`, `_i64`, and `_i128`. + == Examples -.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe integer types. +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe numeric types. ==== [source, c++] ---- @@ -93,6 +157,13 @@ Output: 100000_u32 = 100000 9999999999_u64 = 9999999999 max_u128 = 340282366920938463463374607431768211455 +-42_i8 = -42 +max_i16 = 32767 +min+1_i32 = -2147483647 +max_i64 = 9223372036854775807 +min+1_i128 = -170141183460469231731687303715884105727 +pi_f32 = 3.14 +e_f64 = 2.71828 100_u32 + 50_u32 = 150 6_u32 * 7_u32 = 42 constexpr 255_u8 = 255 diff --git a/examples/literals.cpp b/examples/literals.cpp index c7631e3e..05ea7805 100644 --- a/examples/literals.cpp +++ b/examples/literals.cpp @@ -3,10 +3,12 @@ // https://www.boost.org/LICENSE_1_0.txt // This example demonstrates the use of user-defined literals for -// constructing safe integer types. The literals provide a concise +// constructing safe numeric types. The literals provide a concise // syntax and perform compile-time range checking when possible. #include +#include +#include #include #include #include @@ -16,7 +18,7 @@ int main() using namespace boost::safe_numbers; using namespace boost::safe_numbers::literals; - // Construct safe integers using literal suffixes + // Construct safe unsigned integers using literal suffixes { constexpr auto a {42_u8}; constexpr auto b {1000_u16}; @@ -31,6 +33,32 @@ int main() std::cout << "max_u128 = " << e << std::endl; } + // Signed integer literals. Negative values are formed by applying unary + // minus to a positive literal: -42_i32 parses as -(42_i32). + { + constexpr auto a {-42_i8}; + constexpr auto b {32767_i16}; + constexpr auto c {-2147483647_i32}; + constexpr auto d {9223372036854775807_i64}; + constexpr auto e {-170141183460469231731687303715884105727_i128}; + + std::cout << "-42_i8 = " << a << std::endl; + std::cout << "max_i16 = " << b << std::endl; + std::cout << "min+1_i32 = " << c << std::endl; + std::cout << "max_i64 = " << d << std::endl; + std::cout << "min+1_i128 = " << e << std::endl; + } + + // Floating-point literals. The literal accepts a long double and is + // range-checked against the target type's maximum. + { + constexpr auto pi {3.14_f32}; + constexpr auto e {2.718281828459045_f64}; + + std::cout << "pi_f32 = " << pi << std::endl; + std::cout << "e_f64 = " << e << std::endl; + } + // Literals work naturally in expressions { const auto sum {100_u32 + 50_u32}; @@ -56,6 +84,8 @@ int main() // // auto bad = 256_u8; // throws std::overflow_error (> UINT8_MAX) // auto bad = 70000_u16; // throws std::overflow_error (> UINT16_MAX) + // auto bad = 128_i8; // throws std::overflow_error (> INT8_MAX) + // auto bad = 1.0e40_f32; // throws std::overflow_error (> FLT_MAX) return 0; } diff --git a/include/boost/safe_numbers/detail/int128/detail/common_div.hpp b/include/boost/safe_numbers/detail/int128/detail/common_div.hpp index 11126ac1..dc318c22 100644 --- a/include/boost/safe_numbers/detail/int128/detail/common_div.hpp +++ b/include/boost/safe_numbers/detail/int128/detail/common_div.hpp @@ -447,7 +447,7 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE BOOST_SAFE_NUMBERS_DETAIL_INT128_FO return; } - #else + #endif if (rhs <= UINT32_MAX) { @@ -467,8 +467,6 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE BOOST_SAFE_NUMBERS_DETAIL_INT128_FO quotient = impl::from_words(q); remainder = impl::from_words(u); } - - #endif } template diff --git a/include/boost/safe_numbers/detail/int128/detail/mini_from_chars.hpp b/include/boost/safe_numbers/detail/int128/detail/mini_from_chars.hpp index bb90d2ea..072f6228 100644 --- a/include/boost/safe_numbers/detail/int128/detail/mini_from_chars.hpp +++ b/include/boost/safe_numbers/detail/int128/detail/mini_from_chars.hpp @@ -127,7 +127,6 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE constexpr int from_chars_integer_im overflow_value /= unsigned_base; - overflow_value <<= 1; max_digit %= unsigned_base; // If the only character was a sign abort now @@ -138,48 +137,56 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE constexpr int from_chars_integer_im bool overflowed = false; - std::ptrdiff_t nc = last - next; - constexpr std::ptrdiff_t nd = std::numeric_limits::digits10; + const std::ptrdiff_t nc = last - next; + // For bases 2..10 the first digits10 characters always fit in the unsigned + // target type without overflow, so the per-iteration check is unnecessary + // there: 10^digits10 <= 2^bits, and any base <= 10 packs no more value per + // digit than base 10. For bases above 10 the safe window is shorter than + // digits10 (e.g. only ~24 digits in base 36 fit in 128 bits), so the check + // must run on every iteration. + const std::ptrdiff_t nd { + base <= 10 + ? static_cast(std::numeric_limits::digits10) + : std::ptrdiff_t{0} + }; + + const std::ptrdiff_t fast_limit {nd < nc ? nd : nc}; + std::ptrdiff_t i = 0; + + for (; i < fast_limit; ++i) { - std::ptrdiff_t i = 0; + const auto current_digit = static_cast(digit_from_char(*next)); - for( ; i < nd && i < nc; ++i ) + if (current_digit >= unsigned_base) { - // overflow is not possible in the first nd characters + break; + } - const auto current_digit = static_cast(digit_from_char(*next)); + result = static_cast(result * unsigned_base + current_digit); + ++next; + } - if (current_digit >= unsigned_base) - { - break; - } + for (; i < nc; ++i) + { + const auto current_digit = static_cast(digit_from_char(*next)); - result = static_cast(result * unsigned_base + current_digit); - ++next; + if (current_digit >= unsigned_base) + { + break; } - for( ; i < nc; ++i ) + if (result < overflow_value || (result == overflow_value && current_digit <= max_digit)) { - const auto current_digit = static_cast(digit_from_char(*next)); - - if (current_digit >= unsigned_base) - { - break; - } - - if (result < overflow_value || (result == overflow_value && current_digit <= max_digit)) - { - result = static_cast(result * unsigned_base + current_digit); - } - else - { - overflowed = true; - break; - } - - ++next; + result = static_cast(result * unsigned_base + current_digit); } + else + { + overflowed = true; + break; + } + + ++next; } // Return the parsed value, adding the sign back if applicable diff --git a/include/boost/safe_numbers/literals.hpp b/include/boost/safe_numbers/literals.hpp index 8a87d4cb..5be291b4 100644 --- a/include/boost/safe_numbers/literals.hpp +++ b/include/boost/safe_numbers/literals.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -81,6 +83,93 @@ BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_u128(const char* str) -> u1 return u128{result}; } +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_i8(const unsigned long long int val) -> i8 +{ + if (constexpr unsigned long long int max_value {std::numeric_limits::max()}; val > max_value) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + } + + return static_cast(static_cast(val)); +} + +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_i16(const unsigned long long int val) -> i16 +{ + if (constexpr unsigned long long int max_value {std::numeric_limits::max()}; val > max_value) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + } + + return static_cast(static_cast(val)); +} + +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_i32(const unsigned long long int val) -> i32 +{ + if (constexpr unsigned long long int max_value {std::numeric_limits::max()}; val > max_value) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + } + + return static_cast(static_cast(val)); +} + +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_i64(const unsigned long long int val) -> i64 +{ + if (constexpr unsigned long long int max_value {std::numeric_limits::max()}; val > max_value) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + } + + return static_cast(static_cast(val)); +} + +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_i128(const char* str) -> i128 +{ + int128::int128_t result; + const auto r {int128::detail::from_chars(str, str + int128::detail::strlen(str), result)}; + + switch (r) + { + case EDOM: + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + case EINVAL: + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::invalid_argument, "Invalid conversion from literal"); + default: + static_cast(r); + } + + return i128{result}; +} + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4756) +#endif + +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_f32(const long double val) -> f32 +{ + if (constexpr auto max_value {static_cast(std::numeric_limits::max())}; val > max_value) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + } + + return static_cast(static_cast(val)); +} + +BOOST_SAFE_NUMBERS_EXPORT constexpr auto operator ""_f64(const long double val) -> f64 +{ + if (constexpr auto max_value {static_cast(std::numeric_limits::max())}; val > max_value) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, "Overflow detected in literal construction"); + } + + return static_cast(static_cast(val)); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + } // boost::safe_numbers::literals #endif // BOOST_SAFE_NUMBERS_LITERALS_HPP diff --git a/test/Jamfile b/test/Jamfile index c0ae7e92..241b2850 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -61,6 +61,7 @@ run test_float_streaming.cpp ; run test_float_std_format.cpp ; run test_float_fmt_format.cpp ; run test_float_limits.cpp ; +run test_float_literals.cpp ; compile compile_integer_type_properties.cpp ; run test_unsigned_narrowing_conversions.cpp ; run test_signed_conversions.cpp ; @@ -85,6 +86,8 @@ compile-fail compile_fail_unsigned_construction_from_bool.cpp ; run test_unsigned_streaming.cpp ; run test_unsigned_literals.cpp ; compile-fail compile_fail_unsigned_literals.cpp ; +run test_signed_literals.cpp ; +compile-fail compile_fail_signed_literals.cpp ; run test_unsigned_subtraction.cpp ; run test_unsigned_multiplication.cpp ; run test_unsigned_division.cpp ; diff --git a/test/compile_fail_bounded_float_mixed_ops.cpp b/test/compile_fail_bounded_float_mixed_ops.cpp index dc964eee..df4bbf69 100644 --- a/test/compile_fail_bounded_float_mixed_ops.cpp +++ b/test/compile_fail_bounded_float_mixed_ops.cpp @@ -17,7 +17,7 @@ int main() bounded_float<-2.0f, 2.0f> b {f32{0.5f}}; auto c = a + b; - (void)c; + static_cast(c); return 0; } diff --git a/test/compile_fail_signed_literals.cpp b/test/compile_fail_signed_literals.cpp new file mode 100644 index 00000000..8cfbe070 --- /dev/null +++ b/test/compile_fail_signed_literals.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +using namespace boost::safe_numbers; +using namespace boost::safe_numbers::literals; + +void test() +{ + constexpr auto i8_value {18446744073709551615_i8}; + constexpr auto i16_value {18446744073709551615_i16}; + constexpr auto i32_value {18446744073709551615_i32}; + constexpr auto i64_value {18446744073709551615_i64}; + constexpr auto i128_value {"999999999999999999999999999999999999999"_i128}; + + static_cast(i8_value); + static_cast(i16_value); + static_cast(i32_value); + static_cast(i64_value); + static_cast(i128_value); +} + +int main() +{ + test(); + + return boost::report_errors(); +} diff --git a/test/test_float_literals.cpp b/test/test_float_literals.cpp new file mode 100644 index 00000000..e4892bf6 --- /dev/null +++ b/test/test_float_literals.cpp @@ -0,0 +1,95 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +#include + +#include +#include +#include + +using namespace boost::safe_numbers; +using namespace boost::safe_numbers::literals; + +template +void test(); + +template <> +void test() +{ + constexpr auto good_value {1.5_f32}; + BOOST_TEST(good_value == f32{1.5f}); + + constexpr auto zero_value {0.0_f32}; + BOOST_TEST(zero_value == f32{0.0f}); + + // Scientific-notation literal still routes through the long double overload. + constexpr auto sci_value {1.0e10_f32}; + BOOST_TEST(sci_value == f32{1.0e10f}); + + // A subnormal-adjacent magnitude does not trip the overflow check. + constexpr auto small_value {1.0e-30_f32}; + BOOST_TEST(small_value == f32{1.0e-30f}); + + // Inside float range but in the upper region. + constexpr auto large_value {3.0e38_f32}; + BOOST_TEST(large_value == f32{3.0e38f}); + + // The literal parameter is long double, so a finite long double value that + // exceeds float max must throw before the narrowing cast. + BOOST_TEST_THROWS(1.0e40_f32, std::overflow_error); + + // Just past float max (3.4028e38) also throws. + BOOST_TEST_THROWS(3.5e38_f32, std::overflow_error); + + // A value far past float max throws. + BOOST_TEST_THROWS(1.0e300_f32, std::overflow_error); +} + +template <> +void test() +{ + constexpr auto good_value {1.5_f64}; + BOOST_TEST(good_value == f64{1.5}); + + constexpr auto zero_value {0.0_f64}; + BOOST_TEST(zero_value == f64{0.0}); + + constexpr auto sci_value {1.0e100_f64}; + BOOST_TEST(sci_value == f64{1.0e100}); + + constexpr auto small_value {1.0e-300_f64}; + BOOST_TEST(small_value == f64{1.0e-300}); + + // Inside double range but in the upper region. + constexpr auto large_value {1.0e308_f64}; + BOOST_TEST(large_value == f64{1.0e308}); + + // Overflow can only be expressed as a source-level literal when long double + // has a wider exponent range than double; otherwise the literal token would + // be ill-formed on the host platform (e.g. Apple Silicon, where long double + // matches double). Gate the test on that property so it runs everywhere it + // is meaningful. +#if LDBL_MAX_EXP > DBL_MAX_EXP + BOOST_TEST_THROWS(1.0e310_f64, std::overflow_error); + BOOST_TEST_THROWS(1.0e1000_f64, std::overflow_error); +#endif +} + +int main() +{ + test(); + test(); + + return boost::report_errors(); +} diff --git a/test/test_signed_literals.cpp b/test/test_signed_literals.cpp new file mode 100644 index 00000000..85b82946 --- /dev/null +++ b/test/test_signed_literals.cpp @@ -0,0 +1,150 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +#include + +#include +#include + +using namespace boost::safe_numbers; +using namespace boost::safe_numbers::literals; + +template +void test(); + +template <> +void test() +{ + constexpr auto good_value {42_i8}; + BOOST_TEST_EQ(good_value, i8{42}); + + // Negative values are formed by applying the wrapper's unary minus to the + // positive literal: -42_i8 parses as -(42_i8). + constexpr auto neg_value {-42_i8}; + BOOST_TEST_EQ(neg_value, i8{-42}); + + // INT8_MAX itself is representable. + constexpr auto max_value {127_i8}; + BOOST_TEST_EQ(max_value, i8{127}); + + // INT8_MIN+1 is the lowest negative reachable through the integer UDL, + // because the magnitude (127) fits within INT8_MAX. + constexpr auto min_plus_one {-127_i8}; + BOOST_TEST_EQ(min_plus_one, i8{-127}); + + // INT8_MAX+1 overflows the positive-magnitude check. Note that this also + // means INT8_MIN is unreachable through the UDL: -128_i8 parses as + // -(128_i8) and the inner literal throws before the unary minus runs. + BOOST_TEST_THROWS(128_i8, std::overflow_error); + BOOST_TEST_THROWS(255_i8, std::overflow_error); + BOOST_TEST_THROWS(18446744073709551615_i8, std::overflow_error); +} + +template <> +void test() +{ + constexpr auto good_value {42_i16}; + BOOST_TEST_EQ(good_value, i16{42}); + + constexpr auto neg_value {-42_i16}; + BOOST_TEST_EQ(neg_value, i16{-42}); + + constexpr auto max_value {32767_i16}; + BOOST_TEST_EQ(max_value, i16{32767}); + + constexpr auto min_plus_one {-32767_i16}; + BOOST_TEST_EQ(min_plus_one, i16{-32767}); + + BOOST_TEST_THROWS(32768_i16, std::overflow_error); + BOOST_TEST_THROWS(65535_i16, std::overflow_error); + BOOST_TEST_THROWS(18446744073709551615_i16, std::overflow_error); +} + +template <> +void test() +{ + constexpr auto good_value {42_i32}; + BOOST_TEST_EQ(good_value, i32{42}); + + constexpr auto neg_value {-42_i32}; + BOOST_TEST_EQ(neg_value, i32{-42}); + + constexpr auto max_value {2147483647_i32}; + BOOST_TEST_EQ(max_value, i32{2147483647}); + + constexpr auto min_plus_one {-2147483647_i32}; + BOOST_TEST_EQ(min_plus_one, i32{-2147483647}); + + BOOST_TEST_THROWS(2147483648_i32, std::overflow_error); + BOOST_TEST_THROWS(4294967295_i32, std::overflow_error); + BOOST_TEST_THROWS(18446744073709551615_i32, std::overflow_error); +} + +template <> +void test() +{ + constexpr auto good_value {42_i64}; + BOOST_TEST_EQ(good_value, i64{42}); + + constexpr auto neg_value {-42_i64}; + BOOST_TEST_EQ(neg_value, i64{-42}); + + constexpr auto max_value {9223372036854775807_i64}; + BOOST_TEST_EQ(max_value, i64{9223372036854775807LL}); + + constexpr auto min_plus_one {-9223372036854775807_i64}; + BOOST_TEST_EQ(min_plus_one, i64{-9223372036854775807LL}); + + // INT64_MAX+1 = 2^63 fits in unsigned long long, so the literal token is + // well-formed; the UDL's range check rejects it as overflow. + BOOST_TEST_THROWS(9223372036854775808_i64, std::overflow_error); + BOOST_TEST_THROWS(18446744073709551615_i64, std::overflow_error); +} + +template <> +void test() +{ + // _i128 is the raw-integer UDL form, the same as _u128: it dispatches on + // an integer-form token and receives the digits as a null-terminated + // string. Parsing is delegated to from_chars on int128_t. + constexpr auto good_value {42_i128}; + BOOST_TEST_EQ(good_value, i128{42}); + + constexpr auto neg_value {-42_i128}; + BOOST_TEST_EQ(neg_value, i128{-42}); + + constexpr auto zero_value {0_i128}; + BOOST_TEST_EQ(zero_value, i128{0}); + + // INT128_MAX (2^127 - 1) is representable as a positive literal. + constexpr auto max_value {170141183460469231731687303715884105727_i128}; + BOOST_TEST_EQ(max_value, i128{(std::numeric_limits::max)()}); + + // INT128_MAX+1 (2^127) overflows. As with the smaller signed types, this + // also means INT128_MIN is unreachable through the UDL. + BOOST_TEST_THROWS(170141183460469231731687303715884105728_i128, std::overflow_error); + BOOST_TEST_THROWS(999999999999999999999999999999999999999_i128, std::overflow_error); + BOOST_TEST_THROWS(1000000000000000000000000000000000000000_i128, std::overflow_error); +} + +int main() +{ + test(); + test(); + test(); + test(); + test(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_literals.cpp b/test/test_unsigned_literals.cpp index f593d9ba..f4cf5cfa 100644 --- a/test/test_unsigned_literals.cpp +++ b/test/test_unsigned_literals.cpp @@ -57,6 +57,14 @@ void test() { constexpr auto good_value {42_u128}; BOOST_TEST_EQ(good_value, u128{42u}); + + // UINT128_MAX itself is representable. + constexpr auto max_value {340282366920938463463374607431768211455_u128}; + static_cast(max_value); + + // UINT128_MAX+1 (2^128) must overflow. Regression test for from_chars + // overflow detection at the exact boundary. + BOOST_TEST_THROWS(340282366920938463463374607431768211456_u128, std::overflow_error); BOOST_TEST_THROWS(18446744073709551615184467440737095516151844674407370955161518446744073709551615_u128, std::overflow_error); }