|
| 1 | +// Copyright 2026 Matt Borland |
| 2 | +// Distributed under the Boost Software License, Version 1.0. |
| 3 | +// https://www.boost.org/LICENSE_1_0.txt |
| 4 | + |
| 5 | +// Exercises boost::int128::detail::from_chars across every base it supports |
| 6 | +// (2..36) for both int128_t and uint128_t. Locks in the fix to the per-iteration |
| 7 | +// overflow threshold (the spurious overflow_value <<= 1 was masking MAX+1 |
| 8 | +// overflow in mini_from_chars). |
| 9 | + |
| 10 | +#ifndef BOOST_INT128_BUILD_MODULE |
| 11 | + |
| 12 | +#include <boost/int128/int128.hpp> |
| 13 | +#include <boost/int128/detail/mini_from_chars.hpp> |
| 14 | + |
| 15 | +#else |
| 16 | + |
| 17 | +import boost.int128; |
| 18 | + |
| 19 | +#endif |
| 20 | + |
| 21 | +#include <boost/core/lightweight_test.hpp> |
| 22 | + |
| 23 | +#include <algorithm> |
| 24 | +#include <cerrno> |
| 25 | +#include <limits> |
| 26 | +#include <string> |
| 27 | + |
| 28 | +namespace { |
| 29 | + |
| 30 | +using boost::int128::int128_t; |
| 31 | +using boost::int128::uint128_t; |
| 32 | + |
| 33 | +// Format a uint128_t into a base-N string (lowercase). Self-contained so the |
| 34 | +// test does not pull in boost::charconv just to generate inputs. |
| 35 | +std::string format_unsigned(uint128_t value, int base) |
| 36 | +{ |
| 37 | + if (value == uint128_t{0U}) |
| 38 | + { |
| 39 | + return "0"; |
| 40 | + } |
| 41 | + |
| 42 | + const auto ubase {static_cast<unsigned>(base)}; |
| 43 | + std::string out; |
| 44 | + while (value != uint128_t{0U}) |
| 45 | + { |
| 46 | + const auto digit {static_cast<unsigned>(value % ubase)}; |
| 47 | + const char c {digit < 10U ? static_cast<char>('0' + digit) |
| 48 | + : static_cast<char>('a' + digit - 10U)}; |
| 49 | + out.push_back(c); |
| 50 | + value /= ubase; |
| 51 | + } |
| 52 | + std::reverse(out.begin(), out.end()); |
| 53 | + return out; |
| 54 | +} |
| 55 | + |
| 56 | +std::string format_signed(int128_t value, int base) |
| 57 | +{ |
| 58 | + if (value == int128_t{0}) |
| 59 | + { |
| 60 | + return "0"; |
| 61 | + } |
| 62 | + |
| 63 | + if (value == (std::numeric_limits<int128_t>::min)()) |
| 64 | + { |
| 65 | + // |INT128_MIN| does not fit in int128_t; do the magnitude in uint128_t. |
| 66 | + const uint128_t magnitude {uint128_t{1} << 127U}; |
| 67 | + std::string out {format_unsigned(magnitude, base)}; |
| 68 | + out.insert(out.begin(), '-'); |
| 69 | + return out; |
| 70 | + } |
| 71 | + |
| 72 | + if (value < int128_t{0}) |
| 73 | + { |
| 74 | + std::string out {format_unsigned(static_cast<uint128_t>(-value), base)}; |
| 75 | + out.insert(out.begin(), '-'); |
| 76 | + return out; |
| 77 | + } |
| 78 | + |
| 79 | + return format_unsigned(static_cast<uint128_t>(value), base); |
| 80 | +} |
| 81 | + |
| 82 | +inline std::string format_value(int128_t value, int base) |
| 83 | +{ |
| 84 | + return format_signed(value, base); |
| 85 | +} |
| 86 | + |
| 87 | +inline std::string format_value(uint128_t value, int base) |
| 88 | +{ |
| 89 | + return format_unsigned(value, base); |
| 90 | +} |
| 91 | + |
| 92 | +template <typename T> |
| 93 | +void check_roundtrip(T expected, int base) |
| 94 | +{ |
| 95 | + const std::string s {format_value(expected, base)}; |
| 96 | + |
| 97 | + T parsed {}; |
| 98 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), parsed, base)}; |
| 99 | + |
| 100 | + BOOST_TEST_LT(r, 0); |
| 101 | + BOOST_TEST(parsed == expected); |
| 102 | +} |
| 103 | + |
| 104 | +template <typename T> |
| 105 | +void check_overflow(const std::string& s, int base) |
| 106 | +{ |
| 107 | + T parsed {}; |
| 108 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), parsed, base)}; |
| 109 | + |
| 110 | + BOOST_TEST_EQ(r, EDOM); |
| 111 | +} |
| 112 | + |
| 113 | +void test_uint128_all_bases() |
| 114 | +{ |
| 115 | + constexpr auto max_value {(std::numeric_limits<uint128_t>::max)()}; |
| 116 | + |
| 117 | + for (int base {2}; base <= 36; ++base) |
| 118 | + { |
| 119 | + // Canonical small values. |
| 120 | + check_roundtrip<uint128_t>(uint128_t{0U}, base); |
| 121 | + check_roundtrip<uint128_t>(uint128_t{1U}, base); |
| 122 | + check_roundtrip<uint128_t>(static_cast<uint128_t>(static_cast<unsigned>(base) - 1U), base); |
| 123 | + check_roundtrip<uint128_t>(static_cast<uint128_t>(static_cast<unsigned>(base)), base); |
| 124 | + |
| 125 | + // A handful of mid-range values that span the per-base digit window. |
| 126 | + check_roundtrip<uint128_t>(uint128_t{42U}, base); |
| 127 | + check_roundtrip<uint128_t>(uint128_t{1234567890U}, base); |
| 128 | + check_roundtrip<uint128_t>(uint128_t{0xFFFFFFFFFFFFFFFFULL}, base); |
| 129 | + check_roundtrip<uint128_t>(uint128_t{1U} << 100U, base); |
| 130 | + |
| 131 | + // The boundary itself parses correctly. |
| 132 | + check_roundtrip<uint128_t>(max_value, base); |
| 133 | + |
| 134 | + // MAX with any extra digit appended is at least MAX * base, which |
| 135 | + // overflows uint128_t for every base in [2, 36]. |
| 136 | + const auto max_str {format_unsigned(max_value, base)}; |
| 137 | + check_overflow<uint128_t>(max_str + "0", base); |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +void test_int128_all_bases() |
| 142 | +{ |
| 143 | + constexpr auto max_value {(std::numeric_limits<int128_t>::max)()}; |
| 144 | + constexpr auto min_value {(std::numeric_limits<int128_t>::min)()}; |
| 145 | + |
| 146 | + for (int base {2}; base <= 36; ++base) |
| 147 | + { |
| 148 | + check_roundtrip<int128_t>(int128_t{0}, base); |
| 149 | + check_roundtrip<int128_t>(int128_t{1}, base); |
| 150 | + check_roundtrip<int128_t>(int128_t{-1}, base); |
| 151 | + check_roundtrip<int128_t>(int128_t{42}, base); |
| 152 | + check_roundtrip<int128_t>(int128_t{-42}, base); |
| 153 | + check_roundtrip<int128_t>(int128_t{1234567890}, base); |
| 154 | + check_roundtrip<int128_t>(int128_t{-1234567890}, base); |
| 155 | + |
| 156 | + // Both signed boundaries parse correctly. |
| 157 | + check_roundtrip<int128_t>(max_value, base); |
| 158 | + check_roundtrip<int128_t>(min_value, base); |
| 159 | + |
| 160 | + // Append a digit to push past the magnitude bound on each side. |
| 161 | + const auto max_str {format_signed(max_value, base)}; |
| 162 | + check_overflow<int128_t>(max_str + "0", base); |
| 163 | + |
| 164 | + const auto min_str {format_signed(min_value, base)}; |
| 165 | + check_overflow<int128_t>(min_str + "0", base); |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +void test_decimal_boundaries() |
| 170 | +{ |
| 171 | + // Tight base-10 boundary cases: the spurious <<= 1 in the threshold made |
| 172 | + // these silently produce wrong values instead of returning EDOM. |
| 173 | + |
| 174 | + // UINT128_MAX exactly. |
| 175 | + { |
| 176 | + const std::string s {"340282366920938463463374607431768211455"}; |
| 177 | + uint128_t v {}; |
| 178 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), v)}; |
| 179 | + BOOST_TEST_LT(r, 0); |
| 180 | + BOOST_TEST(v == (std::numeric_limits<uint128_t>::max)()); |
| 181 | + } |
| 182 | + |
| 183 | + // UINT128_MAX + 1. |
| 184 | + check_overflow<uint128_t>("340282366920938463463374607431768211456", 10); |
| 185 | + |
| 186 | + // INT128_MAX exactly. |
| 187 | + { |
| 188 | + const std::string s {"170141183460469231731687303715884105727"}; |
| 189 | + int128_t v {}; |
| 190 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), v)}; |
| 191 | + BOOST_TEST_LT(r, 0); |
| 192 | + BOOST_TEST(v == (std::numeric_limits<int128_t>::max)()); |
| 193 | + } |
| 194 | + |
| 195 | + // INT128_MAX + 1. |
| 196 | + check_overflow<int128_t>("170141183460469231731687303715884105728", 10); |
| 197 | + |
| 198 | + // INT128_MIN exactly. |
| 199 | + { |
| 200 | + const std::string s {"-170141183460469231731687303715884105728"}; |
| 201 | + int128_t v {}; |
| 202 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), v)}; |
| 203 | + BOOST_TEST_LT(r, 0); |
| 204 | + BOOST_TEST(v == (std::numeric_limits<int128_t>::min)()); |
| 205 | + } |
| 206 | + |
| 207 | + // INT128_MIN - 1. |
| 208 | + check_overflow<int128_t>("-170141183460469231731687303715884105729", 10); |
| 209 | +} |
| 210 | + |
| 211 | +void test_invalid_inputs() |
| 212 | +{ |
| 213 | + // Empty range is EINVAL. |
| 214 | + { |
| 215 | + const char* s {""}; |
| 216 | + uint128_t v {}; |
| 217 | + const auto r {boost::int128::detail::from_chars(s, s, v)}; |
| 218 | + BOOST_TEST_EQ(r, EINVAL); |
| 219 | + } |
| 220 | + |
| 221 | + // Lone sign is EINVAL. |
| 222 | + { |
| 223 | + const std::string s {"-"}; |
| 224 | + int128_t v {}; |
| 225 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), v)}; |
| 226 | + BOOST_TEST_EQ(r, EINVAL); |
| 227 | + } |
| 228 | + |
| 229 | + // Leading sign on the unsigned overload is EINVAL. |
| 230 | + { |
| 231 | + const std::string s {"-1"}; |
| 232 | + uint128_t v {}; |
| 233 | + const auto r {boost::int128::detail::from_chars(s.data(), s.data() + s.size(), v)}; |
| 234 | + BOOST_TEST_EQ(r, EINVAL); |
| 235 | + } |
| 236 | +} |
| 237 | + |
| 238 | +} // anonymous namespace |
| 239 | + |
| 240 | +int main() |
| 241 | +{ |
| 242 | + test_uint128_all_bases(); |
| 243 | + test_int128_all_bases(); |
| 244 | + test_decimal_boundaries(); |
| 245 | + test_invalid_inputs(); |
| 246 | + |
| 247 | + return boost::report_errors(); |
| 248 | +} |
0 commit comments