Examples

All the following examples can be found in the example/ folder of the library. Each one is built and run as part of the test suite, and the output shown below is what the program printed.

Basic Construction

Example 1. This example demonstrates every way a uint256 or int256 can be constructed: from a built-in integer, from the four 64-bit words, from a literal or the constant macro, from a stream, and from a floating-point value.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/literals.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>
#include <boost/int256/climits.hpp>
#include <iostream>
#include <limits>
#include <sstream>

int main()
{
    using boost::int256::uint256;

    std::cout << "=== uint256 Construction ===" << std::endl;

    // 1) From a builtin integer type
    constexpr uint256 from_builtin {42U};
    std::cout << "From builtin (42U): " << from_builtin << std::endl;

    // 2) From the four 64-bit words, most significant first
    constexpr uint256 from_parts {UINT64_C(0), UINT64_C(0), UINT64_C(1), UINT64_C(0)};  // 2^64
    std::cout << "From words (0, 0, 1, 0) = 2^64: " << from_parts << std::endl;

    constexpr uint256 max_value {UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX};
    std::cout << "From words (max, max, max, max): " << max_value << std::endl;
    std::cout << "  Equals numeric_limits max? " << std::boolalpha
              << (max_value == (std::numeric_limits<uint256>::max)()) << std::endl;

    // 3) From user-defined literals.
    // The library provides only string-form UDLs
    // For small values like this a string is still parsed rather than direct construction
    // Using the constructors for values that fit in (unsigned) long long should be preferred for performance
    using namespace boost::int256::literals;
    const auto small_literal {12345_U256};
    std::cout << "From literal 12345_U256: " << small_literal << std::endl;

    const auto big_literal {"115792089237316195423570985008687907853269984665640564039457584007913129639935"_u256};
    std::cout << "From string literal (max): " << big_literal << std::endl;

    // 4) From macro (like UINT64_C but for 256-bit), good for values that exceed unsigned long long
    const auto from_macro {BOOST_INT256_UINT256_C(6277101735386680763835789423207666416102355444464034512896)};
    std::cout << "From BOOST_INT256_UINT256_C(2^192): " << from_macro << std::endl;

    // 5) From input stream
    std::stringstream ss;
    ss.str("123456789012345678901234567890123456789012345678901234567890");
    uint256 from_stream;
    ss >> from_stream;
    std::cout << "From stringstream: " << from_stream << std::endl;

    std::cout << "\n=== int256 Construction ===" << std::endl;

    using boost::int256::int256;

    // Signed from a builtin integer type
    constexpr int256 signed_builtin {-42};
    std::cout << "From builtin (-42): " << signed_builtin << std::endl;

    // Signed from the four words, most significant first. The top word is
    // signed, so int256{-1, 0, 0, 0} works; read the sign back with signed_high().
    constexpr int256 min_value {INT64_MIN, UINT64_C(0), UINT64_C(0), UINT64_C(0)};
    std::cout << "From words (INT64_MIN, 0, 0, 0): " << min_value << std::endl;
    std::cout << "  Equals numeric_limits min? " << std::boolalpha
              << (min_value == (std::numeric_limits<int256>::min)()) << std::endl;
    std::cout << "  signed_high(): " << min_value.signed_high() << std::endl;

    // Signed literals. Values that fit in unsigned long long can be written
    // directly; the leading minus is parsed as a unary operator on the
    // literal result (lowercase and uppercase suffixes both work):
    const auto negative_literal {-12345_i256};
    std::cout << "From literal -12345_i256: " << negative_literal << std::endl;

    const auto positive_literal {12345_I256};
    std::cout << "From literal 12345_I256: " << positive_literal << std::endl;

    // For magnitudes beyond unsigned long long, use a string literal or the macro
    const auto large_signed_string {"-99999999999999999999"_i256};
    std::cout << "From string literal: " << large_signed_string << std::endl;

    const auto from_signed_macro {BOOST_INT256_INT256_C(-99999999999999999999)};
    std::cout << "From BOOST_INT256_INT256_C(-99999999999999999999): " << from_signed_macro << std::endl;

    // A raw (unquoted) literal never sees a leading '-': -X_i256 is unary
    // minus applied to X_i256, so X itself must be at most MAX in every
    // base. The minimum value's magnitude, 2^255, is one more than MAX, so
    // -57896...968_i256 and -0x8000...0_i256 (unquoted) are BOTH ill-formed,
    // the same way -9223372036854775808LL is for a built-in long long.
    // A quoted string literal embeds the sign in the digit sequence instead,
    // so it (and the macro, which stringifies its whole argument including
    // the sign) can reach MIN directly:
    const auto min_from_hex {"-0x8000000000000000000000000000000000000000000000000000000000000000"_i256};
    std::cout << "From hex string literal (-0x8, 63 zeros): " << min_from_hex << std::endl;
    std::cout << "  Equals numeric_limits min? " << std::boolalpha
              << (min_from_hex == (std::numeric_limits<int256>::min)()) << std::endl;

    // BOOST_INT256_INT256_MIN and BOOST_INT256_INT256_MAX are the simplest
    // way to spell the extremes and never risk this trap
    std::cout << "BOOST_INT256_INT256_MIN: " << BOOST_INT256_INT256_MIN << std::endl;
    std::cout << "BOOST_INT256_INT256_MAX: " << BOOST_INT256_INT256_MAX << std::endl;
    std::cout << "  MIN equals numeric_limits min? "
              << (BOOST_INT256_INT256_MIN == (std::numeric_limits<int256>::min)()) << std::endl;

    std::cout << "\n=== Default and Copy Construction ===" << std::endl;

    // Default construction (zero-initialized)
    constexpr uint256 default_constructed {};
    constexpr int256 signed_default_constructed {};
    std::cout << "Default constructed (uint256): " << default_constructed << std::endl;
    std::cout << "Default constructed (int256): " << signed_default_constructed << std::endl;

    // Copy construction
    const uint256 copied {from_macro};
    const int256 signed_copied {from_signed_macro};
    std::cout << "Copy constructed (uint256): " << copied << std::endl;
    std::cout << "Copy constructed (int256): " << signed_copied << std::endl;

    std::cout << "\n=== Floating-Point Construction ===" << std::endl;

    // Floating-point construction truncates toward zero, matching the behavior of
    // a static_cast from a floating-point type to a built-in integer.
    constexpr uint256 from_double {12345.9};
    std::cout << "uint256 from 12345.9 (truncated): " << from_double << std::endl;

    constexpr int256 from_negative_double {-12345.9};
    std::cout << "int256 from -12345.9 (truncated toward zero): " << from_negative_double << std::endl;

    // Values that exceed the 64-bit range are routed through the full 256-bit decomposition.
    const double two_to_the_200 {1.6069380442589903e60};  // 2^200
    const uint256 large_from_double {two_to_the_200};
    std::cout << "uint256 from 2^200: " << large_from_double << std::endl;

    std::cout << "\n=== Floating-Point Edge Cases ===" << std::endl;

    // NaN yields zero for both types (mirrors libgcc's __fix(uns)Xfti).
    const double nan_value {std::numeric_limits<double>::quiet_NaN()};
    const uint256 unsigned_from_nan {nan_value};
    const int256 signed_from_nan {nan_value};
    std::cout << "uint256 from NaN: " << unsigned_from_nan << std::endl;
    std::cout << "int256 from NaN: " << signed_from_nan << std::endl;

    // Negative values are clamped to zero when constructing uint256.
    const uint256 unsigned_from_negative {-1.0};
    std::cout << "uint256 from -1.0 (clamped to zero): " << unsigned_from_negative << std::endl;

    // Positive overflow saturates: anything >= 2^256 (including +infinity) becomes the max.
    const double infinity {std::numeric_limits<double>::infinity()};
    const uint256 saturated {infinity};
    std::cout << "uint256 from +infinity (saturates): " << saturated << std::endl;

    const double huge {1e80};  // Well beyond 2^256 (about 1.16e77)
    const uint256 saturated_from_huge {huge};
    std::cout << "uint256 from 1e80 (saturates): " << saturated_from_huge << std::endl;

    // For int256, values >= 2^255 saturate to INT256_MAX and values <= -2^255
    // saturate to INT256_MIN. 1e80 is well beyond 2^255 (~5.79e76) in magnitude.
    const int256 saturated_positive {huge};
    const int256 saturated_negative {-huge};
    std::cout << "int256 from 1e80 (saturates to max): " << saturated_positive << std::endl;
    std::cout << "int256 from -1e80 (saturates to min): " << saturated_negative << std::endl;

    return 0;
}

Output:

=== uint256 Construction ===
From builtin (42U): 42
From words (0, 0, 1, 0) = 2^64: 18446744073709551616
From words (max, max, max, max): 115792089237316195423570985008687907853269984665640564039457584007913129639935
  Equals numeric_limits max? true
From literal 12345_U256: 12345
From string literal (max): 115792089237316195423570985008687907853269984665640564039457584007913129639935
From BOOST_INT256_UINT256_C(2^192): 6277101735386680763835789423207666416102355444464034512896
From stringstream: 123456789012345678901234567890123456789012345678901234567890

=== int256 Construction ===
From builtin (-42): -42
From words (INT64_MIN, 0, 0, 0): -57896044618658097711785492504343953926634992332820282019728792003956564819968
  Equals numeric_limits min? true
  signed_high(): -9223372036854775808
From literal -12345_i256: -12345
From literal 12345_I256: 12345
From string literal: -99999999999999999999
From BOOST_INT256_INT256_C(-99999999999999999999): -99999999999999999999
From hex string literal (-0x8, 63 zeros): -57896044618658097711785492504343953926634992332820282019728792003956564819968
  Equals numeric_limits min? true
BOOST_INT256_INT256_MIN: -57896044618658097711785492504343953926634992332820282019728792003956564819968
BOOST_INT256_INT256_MAX: 57896044618658097711785492504343953926634992332820282019728792003956564819967
  MIN equals numeric_limits min? true

=== Default and Copy Construction ===
Default constructed (uint256): 0
Default constructed (int256): 0
Copy constructed (uint256): 6277101735386680763835789423207666416102355444464034512896
Copy constructed (int256): -99999999999999999999

=== Floating-Point Construction ===
uint256 from 12345.9 (truncated): 12345
int256 from -12345.9 (truncated toward zero): -12345
uint256 from 2^200: 1606938044258990275541962092341162602522202993782792835301376

=== Floating-Point Edge Cases ===
uint256 from NaN: 0
int256 from NaN: 0
uint256 from -1.0 (clamped to zero): 0
uint256 from +infinity (saturates): 115792089237316195423570985008687907853269984665640564039457584007913129639935
uint256 from 1e80 (saturates): 115792089237316195423570985008687907853269984665640564039457584007913129639935
int256 from 1e80 (saturates to max): 57896044618658097711785492504343953926634992332820282019728792003956564819967
int256 from -1e80 (saturates to min): -57896044618658097711785492504343953926634992332820282019728792003956564819968

Basic Arithmetic

Example 2. This example demonstrates the arithmetic operators for both uint256 and int256, starting with the exact product of two 128-bit values, which is the case a 256-bit type exists for.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/iostream.hpp>
#include <iostream>

int main()
{
    // uint256 supports every arithmetic operation one would expect, against another
    // uint256 and against any built-in integer type.
    // See `mixed_type_arithmetic.cpp` for operations with the built-in types.

    using boost::int256::uint256;

    std::cout << "=== The 128 by 128 bit product ===" << std::endl;

    // The exact product of two 128-bit values needs 256 bits, which is the case this
    // library exists for: a 128-bit type wraps here, and a 64-bit type is not even close.
    const uint256 a {UINT64_C(0), UINT64_C(0), UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF)};
    const uint256 b {a};

    std::cout << "a = 2^128 - 1 = " << a << std::endl;
    std::cout << "a * a         = " << (a * a) << std::endl;
    std::cout << "  (that is 2^256 - 2^129 + 1, and it needs all four words)" << std::endl;

    std::cout << "\n=== Addition, subtraction, multiplication ===" << std::endl;

    const uint256 x {1000000000000ULL};  // 1 trillion
    const uint256 y {999999999999ULL};   // Just under 1 trillion

    std::cout << "x = " << x << std::endl;
    std::cout << "y = " << y << std::endl;
    std::cout << "x + y = " << (x + y) << std::endl;
    std::cout << "x - y = " << (x - y) << std::endl;
    std::cout << "x * y = " << (x * y) << std::endl;

    std::cout << "\n=== Division and modulo ===" << std::endl;

    // Division returns the truncated quotient, exactly as it does for a built-in
    // unsigned type. See cstdlib.cpp for computing both halves in one division.
    std::cout << "x / 7 = " << (x / 7U) << std::endl;
    std::cout << "x % 7 = " << (x % 7U) << std::endl;
    std::cout << "(a * a) / a = " << ((a * a) / a) << std::endl;
    std::cout << "(a * a) % a = " << ((a * a) % a) << std::endl;

    std::cout << "\n=== Comparisons ===" << std::endl;

    std::cout << "x > y:  " << std::boolalpha << (x > y) << std::endl;
    std::cout << "x == y: " << (x == y) << std::endl;
    std::cout << "x != y: " << (x != y) << std::endl;
    std::cout << "b == a: " << (b == a) << std::endl;

    std::cout << "\n=== Compound assignment operators ===" << std::endl;

    uint256 z {100U};
    std::cout << "z = " << z << std::endl;

    z += 50U;
    std::cout << "z += 50: " << z << std::endl;

    z -= 25U;
    std::cout << "z -= 25: " << z << std::endl;

    z *= 2U;
    std::cout << "z *= 2:  " << z << std::endl;

    z /= 5U;
    std::cout << "z /= 5:  " << z << std::endl;

    z %= 7U;
    std::cout << "z %= 7:  " << z << std::endl;

    std::cout << "\n=== Increment and decrement ===" << std::endl;

    uint256 counter {10U};
    std::cout << "counter = " << counter << std::endl;
    std::cout << "++counter = " << ++counter << std::endl;
    std::cout << "counter++ = " << counter++ << std::endl;
    std::cout << "counter = " << counter << std::endl;
    std::cout << "--counter = " << --counter << std::endl;

    std::cout << "\n=== Unary operators ===" << std::endl;

    // Negation is modulo 2^256, so it is the two's complement of the value.
    const uint256 one {1U};
    std::cout << "-one = " << -one << std::endl;
    std::cout << "~one = " << ~one << std::endl;

    // abs is provided for parity with the signed type and returns its argument.
    std::cout << "abs(x) = " << boost::int256::abs(x) << std::endl;

    std::cout << "\n=== Signed 256-bit Arithmetic ===" << std::endl;

    using boost::int256::int256;

    int256 sx {1000000000000LL};   // 1 trillion
    int256 sy {-999999999999LL};   // Just under negative 1 trillion

    std::cout << "sx = " << sx << std::endl;
    std::cout << "sy = " << sy << std::endl;

    std::cout << "\nAddition and Subtraction:" << std::endl;
    std::cout << "sx + sy = " << (sx + sy) << std::endl;
    std::cout << "sx - sy = " << (sx - sy) << std::endl;

    std::cout << "\nMultiplication:" << std::endl;
    std::cout << "sx * sy = " << (sx * sy) << std::endl;

    std::cout << "\nDivision and Modulo:" << std::endl;
    // The quotient truncates toward zero and the remainder takes the sign of
    // the dividend, exactly as for a built-in signed type.
    std::cout << "sx / 7 = " << (sx / 7) << std::endl;
    std::cout << "sx % 7 = " << (sx % 7) << std::endl;
    std::cout << "sy / 7 = " << (sy / 7) << std::endl;
    std::cout << "sy % 7 = " << (sy % 7) << std::endl;

    std::cout << "\nComparisons:" << std::endl;
    std::cout << "sx > sy:  " << (sx > sy) << std::endl;
    std::cout << "sx == sy: " << (sx == sy) << std::endl;
    std::cout << "sx != sy: " << (sx != sy) << std::endl;

    std::cout << "\nNegative values and absolute value:" << std::endl;
    const int256 negative {-42};
    std::cout << "negative = " << negative << std::endl;
    std::cout << "abs(negative) = " << boost::int256::abs(negative) << std::endl;

    std::cout << "\nCompound assignment operators:" << std::endl;
    int256 sz {100};
    std::cout << "sz = " << sz << std::endl;

    sz += 50;
    std::cout << "sz += 50: " << sz << std::endl;

    sz -= 125;
    std::cout << "sz -= 125: " << sz << std::endl;

    sz *= 2;
    std::cout << "sz *= 2:  " << sz << std::endl;

    sz /= 5;
    std::cout << "sz /= 5:  " << sz << std::endl;

    sz %= 7;
    std::cout << "sz %= 7:  " << sz << std::endl;

    std::cout << "\nIncrement and Decrement:" << std::endl;
    int256 signed_counter {-3};
    std::cout << "signed_counter = " << signed_counter << std::endl;
    std::cout << "++signed_counter = " << ++signed_counter << std::endl;
    std::cout << "signed_counter++ = " << signed_counter++ << std::endl;
    std::cout << "signed_counter = " << signed_counter << std::endl;
    std::cout << "--signed_counter = " << --signed_counter << std::endl;

    return 0;
}

Output:

=== The 128 by 128 bit product ===
a = 2^128 - 1 = 340282366920938463463374607431768211455
a * a         = 115792089237316195423570985008687907852589419931798687112530834793049593217025
  (that is 2^256 - 2^129 + 1, and it needs all four words)

=== Addition, subtraction, multiplication ===
x = 1000000000000
y = 999999999999
x + y = 1999999999999
x - y = 1
x * y = 999999999999000000000000

=== Division and modulo ===
x / 7 = 142857142857
x % 7 = 1
(a * a) / a = 340282366920938463463374607431768211455
(a * a) % a = 0

=== Comparisons ===
x > y:  true
x == y: false
x != y: true
b == a: true

=== Compound assignment operators ===
z = 100
z += 50: 150
z -= 25: 125
z *= 2:  250
z /= 5:  50
z %= 7:  1

=== Increment and decrement ===
counter = 10
++counter = 11
counter++ = 11
counter = 12
--counter = 11

=== Unary operators ===
-one = 115792089237316195423570985008687907853269984665640564039457584007913129639935
~one = 115792089237316195423570985008687907853269984665640564039457584007913129639934
abs(x) = 1000000000000

=== Signed 256-bit Arithmetic ===
sx = 1000000000000
sy = -999999999999

Addition and Subtraction:
sx + sy = 1
sx - sy = 1999999999999

Multiplication:
sx * sy = -999999999999000000000000

Division and Modulo:
sx / 7 = 142857142857
sx % 7 = 1
sy / 7 = -142857142857
sy % 7 = 0

Comparisons:
sx > sy:  true
sx == sy: false
sx != sy: true

Negative values and absolute value:
negative = -42
abs(negative) = 42

Compound assignment operators:
sz = 100
sz += 50: 150
sz -= 125: 25
sz *= 2:  50
sz /= 5:  10
sz %= 7:  3

Increment and Decrement:
signed_counter = -3
++signed_counter = -2
signed_counter++ = -2
signed_counter = -1
--signed_counter = -2

IO Streaming

Example 3. This example demonstrates stream insertion and extraction for both uint256 and int256, the <ios> manipulators that control base, case, base prefix, width and fill, and int256’s sign-magnitude formatting (the sign always comes before the base prefix) and `showpos.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>

int main()
{
    using boost::int256::uint256;
    using boost::int256::int256;

    std::cout << "=== Basic Streaming ===" << std::endl;

    // uint256 streams as one would expect from a built-in integer type
    constexpr uint256 small_value {42U};
    std::cout << "Small value: " << small_value << std::endl;

    // The <ios> base manipulators are honored
    constexpr uint256 value {UINT64_C(0), UINT64_C(0), UINT64_C(0x1), UINT64_MAX};
    std::cout << "Value (dec): " << value << '\n'
              << "Value (hex): " << std::hex << value << '\n'
              << "Value (oct): " << std::oct << value << std::endl;

    // Hex can also be uppercased, and a base prefix requested
    std::cout << "Value (upper hex): " << std::hex << std::uppercase << value << '\n'
              << "Value (showbase):  " << std::showbase << value << std::endl;

    // And returned to the default formatting
    std::cout << "Value (dec again): " << std::dec << std::nouppercase << std::noshowbase << value << std::endl;

    std::cout << "\n=== Large Values (Beyond 128 bits) ===" << std::endl;

    // 2^128 is the first value that does not fit in a 128-bit type
    constexpr uint256 two_to_128 {uint256{1U} << 128U};
    std::cout << "2^128 = " << two_to_128 << std::endl;

    constexpr uint256 two_to_200 {uint256{1U} << 200U};
    std::cout << "2^200 = " << two_to_200 << std::endl;

    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};
    std::cout << "uint256 max = " << max_value << std::endl;

    std::cout << "\n=== String Conversion with std::stringstream ===" << std::endl;

    std::ostringstream oss;
    oss << two_to_200;
    const auto str {oss.str()};
    std::cout << "uint256 to string: \"" << str << "\"" << std::endl;

    std::istringstream iss {"123456789012345678901234567890123456789012345678901234567890"};
    uint256 parsed_value {};
    iss >> parsed_value;
    std::cout << "String to uint256: " << parsed_value << std::endl;

    std::cout << "\n=== Round-trip Conversion ===" << std::endl;

    constexpr uint256 original {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678),
                                UINT64_C(0x0F1E2D3C4B5A6978), UINT64_C(0x8796A5B4C3D2E1F0)};
    std::ostringstream oss2;
    oss2 << original;
    const auto original_str {oss2.str()};

    std::istringstream iss2 {original_str};
    uint256 round_tripped {};
    iss2 >> round_tripped;

    std::cout << "Original:      " << original << std::endl;
    std::cout << "As string:     \"" << original_str << "\"" << std::endl;
    std::cout << "Round-tripped: " << round_tripped << std::endl;
    std::cout << "Match: " << std::boolalpha << (original == round_tripped) << std::endl;

    std::cout << "\n=== Width, Fill and Alignment ===" << std::endl;

    // The width and fill manipulators apply to the whole number
    std::cout << "[" << std::setw(30) << std::setfill('.') << small_value << "]" << std::endl;
    std::cout << "[" << std::left << std::setw(30) << std::setfill('.') << small_value << "]" << std::endl;
    std::cout << std::right << std::setfill(' ');

    std::cout << "\n=== Basic Streaming with int256 ===" << std::endl;

    // int256 streams sign-magnitude in every base: the sign always comes
    // before any base prefix (-0xff, not 0x-ff, which is what Boost.Int128
    // prints before its own fix)
    constexpr int256 negative_value {-255};
    std::cout << "Negative value (dec):          " << negative_value << '\n'
              << "Negative value (hex, showbase): " << std::hex << std::showbase << negative_value << '\n'
              << "Negative value (oct, showbase): " << std::oct << negative_value << std::endl;
    std::cout << std::dec << std::noshowbase;

    // showpos prints '+' for a non-negative int256 in decimal only; uint256
    // never prints a '+', matching the built-in unsigned types
    constexpr int256 positive_value {255};
    std::cout << "\nshowpos with int256 (dec):  " << std::showpos << positive_value << std::endl;
    std::cout << "showpos with int256 (hex):  " << std::hex << positive_value << std::dec << std::endl;
    std::cout << std::noshowpos;
    std::cout << "showpos has no effect on uint256: " << std::showpos << small_value << std::endl;
    std::cout << std::noshowpos;

    std::cout << "\n=== Large Negative Values (Beyond 128 bits) ===" << std::endl;

    constexpr int256 min_value {(std::numeric_limits<int256>::min)()};
    std::cout << "int256 min = " << min_value << std::endl;
    std::cout << "int256 min (hex, showbase) = " << std::hex << std::showbase << min_value
              << std::dec << std::noshowbase << std::endl;

    // MIN / -1 is defined (wraps to MIN), unlike the built-in signed types
    std::cout << "int256 min / -1 = " << (min_value / int256{-1}) << std::endl;
    std::cout << "Equals min again: " << std::boolalpha << (min_value / int256{-1} == min_value) << std::endl;

    std::cout << "\n=== Parsing a Negative Value, Including a Prefix ===" << std::endl;

    // operator>> accepts a leading '-', including before a 0x or 0 prefix
    std::istringstream neg_dec {"-123456789012345678901234567890123456789012345678901234567890"};
    int256 parsed_negative {};
    neg_dec >> parsed_negative;
    std::cout << "Parsed decimal: " << parsed_negative << std::endl;

    std::istringstream neg_hex {"-ff"};
    int256 parsed_negative_hex {};
    neg_hex >> std::hex >> parsed_negative_hex;
    std::cout << "Parsed hex \"-ff\": " << std::dec << parsed_negative_hex << std::endl;

    std::cout << "\n=== Round-trip Conversion with int256 ===" << std::endl;

    constexpr int256 original_signed {INT64_MIN, UINT64_C(0xCAFEBABE12345678),
                                      UINT64_C(0x0F1E2D3C4B5A6978), UINT64_C(0x8796A5B4C3D2E1F0)};
    std::ostringstream oss3;
    oss3 << original_signed;
    const auto original_signed_str {oss3.str()};

    std::istringstream iss3 {original_signed_str};
    int256 round_tripped_signed {};
    iss3 >> round_tripped_signed;

    std::cout << "Original:      " << original_signed << std::endl;
    std::cout << "As string:     \"" << original_signed_str << "\"" << std::endl;
    std::cout << "Round-tripped: " << round_tripped_signed << std::endl;
    std::cout << "Match: " << std::boolalpha << (original_signed == round_tripped_signed) << std::endl;

    return 0;
}

Output:

=== Basic Streaming ===
Small value: 42
Value (dec): 36893488147419103231
Value (hex): 1ffffffffffffffff
Value (oct): 3777777777777777777777
Value (upper hex): 1FFFFFFFFFFFFFFFF
Value (showbase):  0X1FFFFFFFFFFFFFFFF
Value (dec again): 36893488147419103231

=== Large Values (Beyond 128 bits) ===
2^128 = 340282366920938463463374607431768211456
2^200 = 1606938044258990275541962092341162602522202993782792835301376
uint256 max = 115792089237316195423570985008687907853269984665640564039457584007913129639935

=== String Conversion with std::stringstream ===
uint256 to string: "1606938044258990275541962092341162602522202993782792835301376"
String to uint256: 123456789012345678901234567890123456789012345678901234567890

=== Round-trip Conversion ===
Original:      23450803645956985397271062114592904319886246494160595176910461526512
As string:     "23450803645956985397271062114592904319886246494160595176910461526512"
Round-tripped: 23450803645956985397271062114592904319886246494160595176910461526512
Match: true

=== Width, Fill and Alignment ===
[............................42]
[42............................]

=== Basic Streaming with int256 ===
Negative value (dec):          -255
Negative value (hex, showbase): -0xff
Negative value (oct, showbase): -0377

showpos with int256 (dec):  +255
showpos with int256 (hex):  ff
showpos has no effect on uint256: 42

=== Large Negative Values (Beyond 128 bits) ===
int256 min = -57896044618658097711785492504343953926634992332820282019728792003956564819968
int256 min (hex, showbase) = -0x8000000000000000000000000000000000000000000000000000000000000000
int256 min / -1 = -57896044618658097711785492504343953926634992332820282019728792003956564819968
Equals min again: true

=== Parsing a Negative Value, Including a Prefix ===
Parsed decimal: -123456789012345678901234567890123456789012345678901234567890
Parsed hex "-ff": -255

=== Round-trip Conversion with int256 ===
Original:      -57896044618658097706808068680902951904513746364596002697710521812135923490320
As string:     "-57896044618658097706808068680902951904513746364596002697710521812135923490320"
Round-tripped: -57896044618658097706808068680902951904513746364596002697710521812135923490320
Match: true

Rollover Behavior

Example 4. This example demonstrates that both uint256 and int256 are modulo types: every operation wraps modulo 2256 and nothing at the boundary is undefined, including int256’s `MIN / -1, MIN * -1 and -MIN, the one signed overflow this library defines.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example demonstrates the rollover behavior of uint256

#include <boost/int256.hpp>
#include <iostream>
#include <limits>

int main()
{
    using boost::int256::uint256;
    using boost::int256::int256;

    constexpr uint256 max_value {(std::numeric_limits<uint256>::max)()};
    constexpr uint256 min_value {(std::numeric_limits<uint256>::min)()};

    std::cout << "=== uint256 behavior ===" << std::endl;

    // uint256 is a modulo type, exactly like the built-in unsigned integers: every
    // operation is reduced modulo 2^256 and nothing is undefined at the boundary.
    std::cout << "is_modulo: " << std::boolalpha
              << std::numeric_limits<uint256>::is_modulo << "\n\n";

    std::cout << "Max of uint256: " << max_value << '\n'
              << "Max + 1U: " << max_value + 1U << "\n\n";

    std::cout << "Min of uint256: " << min_value << '\n'
              << "Min - 1U: " << min_value - 1U << "\n\n";

    // The same holds for multiplication, which is where a 256-bit type is easiest to
    // overflow: the square of anything at or above 2^128 does not fit.
    constexpr uint256 two_to_the_128 {uint256{1U} << 128U};

    std::cout << "2^128: " << two_to_the_128 << '\n'
              << "2^128 * 2^128 (wraps to zero): " << two_to_the_128 * two_to_the_128 << '\n' << std::endl;

    std::cout << "=== int256 behavior ===" << std::endl;

    // int256 is also a modulo type: the arithmetic operators wrap in the
    // unsigned domain, so there is no signed-overflow undefined behavior the
    // way there is for a built-in signed integer.
    std::cout << "is_modulo: " << std::boolalpha
              << std::numeric_limits<int256>::is_modulo << "\n\n";

    constexpr int256 max_signed_value {(std::numeric_limits<int256>::max)()};
    constexpr int256 min_signed_value {(std::numeric_limits<int256>::min)()};

    std::cout << "Max of int256: " << max_signed_value << '\n'
              << "Max + 1: " << max_signed_value + 1 << "\n";

    std::cout << "\nMin of int256: " << min_signed_value << '\n'
              << "Min - 1: " << min_signed_value - 1 << '\n' << std::endl;

    // MIN / -1 is the one signed overflow this library defines rather than
    // leaving undefined: it wraps back to MIN, matching every other
    // overflowing signed operation such as MIN * -1.
    std::cout << "MIN / -1: " << min_signed_value / int256{-1} << '\n'
              << "MIN * -1: " << min_signed_value * int256{-1} << '\n'
              << "-MIN:     " << -min_signed_value << std::endl;

    return 0;
}

Output:

=== uint256 behavior ===
is_modulo: true

Max of uint256: 115792089237316195423570985008687907853269984665640564039457584007913129639935
Max + 1U: 0

Min of uint256: 0
Min - 1U: 115792089237316195423570985008687907853269984665640564039457584007913129639935

2^128: 340282366920938463463374607431768211456
2^128 * 2^128 (wraps to zero): 0

=== int256 behavior ===
is_modulo: true

Max of int256: 57896044618658097711785492504343953926634992332820282019728792003956564819967
Max + 1: -57896044618658097711785492504343953926634992332820282019728792003956564819968

Min of int256: -57896044618658097711785492504343953926634992332820282019728792003956564819968
Min - 1: 57896044618658097711785492504343953926634992332820282019728792003956564819967

MIN / -1: -57896044618658097711785492504343953926634992332820282019728792003956564819968
MIN * -1: -57896044618658097711785492504343953926634992332820282019728792003956564819968
-MIN:     -57896044618658097711785492504343953926634992332820282019728792003956564819968

<bit> support (Bitwise Operations)

Example 5. This example demonstrates the bit manipulation functions, all of which are constexpr and span all four words.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// Individual headers

#include <boost/int256/int256.hpp>
#include <boost/int256/bit.hpp>
#include <boost/int256/iostream.hpp>

// Or you can do a single header

// #include <boost/int256.hpp>

int main()
{
    using boost::int256::uint256;

    constexpr uint256 x {1U};

    // Every function in <boost/int256/bit.hpp> is constexpr

    // Does the value have only a single bit set?
    static_assert(boost::int256::has_single_bit(x), "Should have one bit");

    // How many zeros from the left
    static_assert(boost::int256::countl_zero(x) == 255, "Should be 255");

    // The bit width of the value
    // 1 + 1 is 10 in binary which is 2 bits wide
    static_assert(boost::int256::bit_width(x + x) == 2, "2 bits wide");

    // The largest power of two not greater than the input value
    static_assert(boost::int256::bit_floor(3U * x) == 2U, "2 < 3");

    // The smallest power of two not smaller than the input value
    static_assert(boost::int256::bit_ceil(5U * x) == 8U, "8 > 5");

    // How many zeros from the right?
    static_assert(boost::int256::countr_zero(2U * x) == 1, "1 zero to the right of 10");

    // How many 1-bits in the value
    static_assert(boost::int256::popcount(7U * x) == 3, "111");

    // The counts span all four words: a bit set in the top word is 192 bits from the bottom
    static_assert(boost::int256::countr_zero(x << 192U) == 192, "Top word");
    static_assert(boost::int256::countl_zero(x << 192U) == 63, "Top word");
    static_assert(boost::int256::bit_width(x << 255U) == 256, "The whole width");

    // Rotation is over the full 256 bits, and the count is taken modulo 256
    static_assert(boost::int256::rotl(x, 256) == x, "A full turn is the identity");
    static_assert(boost::int256::rotl(x, -1) == boost::int256::rotr(x, 1), "A negative count reverses");
    static_assert(boost::int256::rotr(x, 1) == (x << 255U), "One bit right of bit zero is bit 255");

    // Swap the bytes
    // Create a value with a distinct byte pattern, most significant word first
    constexpr uint256 original {UINT64_C(0x0123456789ABCDEF), UINT64_C(0xFEDCBA9876543210),
                                UINT64_C(0x1122334455667788), UINT64_C(0x99AABBCCDDEEFF00)};

    // Expected result after byteswap: every byte of the 32 is reversed, so the words
    // swap places and each one is individually byte reversed
    constexpr uint256 expected {UINT64_C(0x00FFEEDDCCBBAA99), UINT64_C(0x8877665544332211),
                                UINT64_C(0x1032547698BADCFE), UINT64_C(0xEFCDAB8967452301)};

    static_assert(boost::int256::byteswap(original) == expected, "Mismatched byteswap");
    static_assert(boost::int256::byteswap(expected) == original, "Mismatched byteswap");

    // The same functions of course work at run time
    std::cout << "popcount(max)        = " << boost::int256::popcount(~uint256{}) << std::endl;
    std::cout << "countl_zero(1)       = " << boost::int256::countl_zero(x) << std::endl;
    std::cout << "countr_zero(2^192)   = " << boost::int256::countr_zero(x << 192U) << std::endl;
    std::cout << "bit_width(2^255)     = " << boost::int256::bit_width(x << 255U) << std::endl;
    std::cout << "bit_ceil(5)          = " << boost::int256::bit_ceil(5U * x) << std::endl;
    std::cout << "has_single_bit(2^99) = " << std::boolalpha
              << boost::int256::has_single_bit(x << 99U) << std::endl;
    std::cout << "byteswap round trip  = "
              << (boost::int256::byteswap(boost::int256::byteswap(original)) == original) << std::endl;

    return 0;
}

Output:

popcount(max)        = 256
countl_zero(1)       = 255
countr_zero(2^192)   = 192
bit_width(2^255)     = 256
bit_ceil(5)          = 8
has_single_bit(2^99) = true
byteswap round trip  = true

Byte Order Conversions

Example 6. This example demonstrates converting a value to and from a 32-byte buffer in big-endian, little-endian, and native order, reading a SHA-256 digest as an integer, and the same conversions on int256, whose two’s complement bit pattern needs no special handling.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/byte_conversions.hpp>
#include <boost/int256/iostream.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>

// Prints the bytes of an array in the order they are stored
template <typename Bytes>
void print_bytes(const char* label, const Bytes& bytes)
{
    std::cout << label;

    for (const auto byte : bytes)
    {
        std::cout << ' ' << std::hex << std::setfill('0') << std::setw(2) << static_cast<unsigned>(byte);
    }

    std::cout << std::dec << std::endl;
}

int main()
{
    using boost::int256::uint256;
    using boost::int256::int256;

    // The 32 bytes 01 02 ... 20 read as a big-endian value, four words most
    // significant first
    constexpr uint256 value {UINT64_C(0x0102030405060708), UINT64_C(0x090A0B0C0D0E0F10),
                             UINT64_C(0x1112131415161718), UINT64_C(0x191A1B1C1D1E1F20)};

    std::cout << "=== Byte arrays ===" << std::endl;

    // The byte order of the array is the requested one on every platform
    print_bytes("to_be_bytes:", boost::int256::to_be_bytes(value));
    print_bytes("to_le_bytes:", boost::int256::to_le_bytes(value));

    // Native order is the object representation of the value. On a little-endian host
    // it matches to_le_bytes; on a big-endian host the bytes of each word are big-endian
    // while the words themselves stay least significant first, because the word order
    // of uint256 is fixed on every platform.
    print_bytes("to_ne_bytes:", boost::int256::to_ne_bytes(value));

    std::cout << "\n=== Reading a value back out of bytes ===" << std::endl;

    constexpr std::array<std::uint8_t, sizeof(uint256)> wire
    {{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2C
    }};

    // The target type is given explicitly, and the byte count has to match
    std::cout << "from_be_bytes: " << boost::int256::from_be_bytes<uint256>(wire) << std::endl;
    std::cout << "from_le_bytes: " << boost::int256::from_le_bytes<uint256>(wire) << std::endl;

    // Everything is constexpr, so a whole round trip can be checked at compile time
    static_assert(boost::int256::from_be_bytes<uint256>(boost::int256::to_be_bytes(value)) == value,
                  "Round trip through big-endian bytes");
    static_assert(boost::int256::from_le_bytes<uint256>(boost::int256::to_le_bytes(value)) == value,
                  "Round trip through little-endian bytes");
    static_assert(boost::int256::from_ne_bytes<uint256>(boost::int256::to_ne_bytes(value)) == value,
                  "Round trip through native bytes");

    std::cout << "\n=== Signed values ===" << std::endl;

    // The two's complement bit pattern is what gets reversed, so a negative
    // int256 needs no special handling: the sign bit is just bit 63 of the
    // last byte written (or the first, for to_be_bytes).
    constexpr int256 negative {-300};

    print_bytes("to_be_bytes(-300):", boost::int256::to_be_bytes(negative));
    std::cout << "from_be_bytes:     "
              << boost::int256::from_be_bytes<int256>(boost::int256::to_be_bytes(negative)) << std::endl;

    static_assert(boost::int256::from_be_bytes<int256>(boost::int256::to_be_bytes(negative)) == negative,
                  "Round trip through big-endian bytes (signed)");

    // uint256 and int256 share the same 32-byte layout, so the same wire bytes
    // convert to either type; only the interpretation of the top bit differs
    constexpr std::array<std::uint8_t, sizeof(int256)> negative_one_wire
    {{
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
    }};
    std::cout << "As uint256 (max): " << boost::int256::from_be_bytes<uint256>(negative_one_wire) << std::endl;
    std::cout << "As int256 (-1):   " << boost::int256::from_be_bytes<int256>(negative_one_wire) << std::endl;

    std::cout << "\n=== Whole value conversions ===" << std::endl;

    // to_be and to_le produce a value whose object representation is in the
    // requested order, which is what a memcpy into a packet buffer wants.
    // The value itself is only meaningful again after the matching from_be / from_le.
    const auto big_endian_image {boost::int256::to_be(value)};

    print_bytes("object representation of to_be(value):", boost::int256::to_ne_bytes(big_endian_image));
    std::cout << "from_be recovers: " << boost::int256::from_be(big_endian_image) << std::endl;
    std::cout << "value:            " << value << std::endl;

    // Any byte-like element type can be requested, which is convenient when the
    // surrounding buffer is not made of std::uint8_t
    const auto as_char {boost::int256::to_le_bytes<char>(value)};
    std::cout << "\nfrom_le_bytes over a char buffer: "
              << boost::int256::from_le_bytes<uint256>(as_char.data()) << std::endl;

    // A 256-bit hash is the obvious case: the digest arrives as 32 big-endian bytes
    // and becomes an integer that can be compared, ordered, and reduced
    constexpr std::array<std::uint8_t, 32> digest
    {{
        0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14,
        0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
        0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C,
        0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55
    }};

    const auto digest_value {boost::int256::from_be_bytes<uint256>(digest)};
    std::cout << "\nSHA-256 of the empty string as an integer:" << std::endl;
    std::cout << "  decimal: " << digest_value << std::endl;
    std::cout << "  hex:     " << std::hex << digest_value << std::dec << std::endl;
    std::cout << "  mod 10^9 + 7: " << digest_value % 1000000007U << std::endl;

    return 0;
}

Output:

=== Byte arrays ===
to_be_bytes: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20
to_le_bytes: 20 1f 1e 1d 1c 1b 1a 19 18 17 16 15 14 13 12 11 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01
to_ne_bytes: 20 1f 1e 1d 1c 1b 1a 19 18 17 16 15 14 13 12 11 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01

=== Reading a value back out of bytes ===
from_be_bytes: 300
from_le_bytes: 19903532184728499472755846345868977080796606098303847563239893857561361776640

=== Signed values ===
to_be_bytes(-300): ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff fe d4
from_be_bytes:     -300
As uint256 (max): 115792089237316195423570985008687907853269984665640564039457584007913129639935
As int256 (-1):   -1

=== Whole value conversions ===
object representation of to_be(value): 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20
from_be recovers: 455867356320691211509944977504407603390036387149619137164185182714736811808
value:            455867356320691211509944977504407603390036387149619137164185182714736811808

from_le_bytes over a char buffer: 455867356320691211509944977504407603390036387149619137164185182714736811808

SHA-256 of the empty string as an integer:
  decimal: 102987336249554097029535212322581322789799900648198034993379397001115665086549
  hex:     e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  mod 10^9 + 7: 353860983

<numeric> support (Saturating Arithmetic)

Example 7. This example demonstrates saturating arithmetic, which clamps at the limits of the type instead of wrapping, and saturating_cast to a narrower type.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// Individual headers

#include <boost/int256/int256.hpp>
#include <boost/int256/numeric.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>

// Or you can do a single header

// #include <boost/int256.hpp>

#include <cstdint>
#include <iostream>
#include <limits>
#include <type_traits>

int main()
{
    using boost::int256::uint256;

    // std::numeric_limits is specialized for the type
    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};
    static_assert(std::is_same<decltype(max_value), const uint256>::value, "Types should match");

    std::cout << "=== Saturating Arithmetic ===" << std::endl;
    std::cout << "uint256 max = " << max_value << std::endl;

    // Saturating arithmetic clamps to max on overflow and to zero on underflow
    // rather than wrapping the way the operators do
    std::cout << "\n=== Saturating Addition and Subtraction ===" << std::endl;
    std::cout << "saturating_add(max, max) = " << boost::int256::saturating_add(max_value, max_value)
              << "\n  (saturates to max; max + max would wrap to max - 1)" << std::endl;
    std::cout << "saturating_sub(0, max)   = " << boost::int256::saturating_sub(uint256{0U}, max_value)
              << "\n  (saturates to 0; 0 - max would wrap to 1)" << std::endl;

    std::cout << "\n=== Saturating Multiplication ===" << std::endl;

    // A 256-bit type is easiest to overflow through multiplication: any two values
    // at or above 2^128 have a product that does not fit
    constexpr uint256 two_to_128 {uint256{1U} << 128U};
    std::cout << "saturating_mul(2^128, 2^128) = " << boost::int256::saturating_mul(two_to_128, two_to_128)
              << "\n  (saturates to max; the operator wraps to 0)" << std::endl;
    std::cout << "saturating_mul(max, 2)       = " << boost::int256::saturating_mul(max_value, uint256{2U})
              << std::endl;
    std::cout << "saturating_mul(2^100, 2^100) = "
              << boost::int256::saturating_mul(uint256{1U} << 100U, uint256{1U} << 100U)
              << "\n  (fits exactly, so nothing saturates)" << std::endl;

    std::cout << "\n=== Saturating Division ===" << std::endl;

    // An unsigned quotient is never larger than the dividend, so saturating_div is
    // provided for interface parity and simply divides
    std::cout << "saturating_div(max, 3) = " << boost::int256::saturating_div(max_value, uint256{3U}) << std::endl;

    std::cout << "\n=== Saturating Casts ===" << std::endl;

    // saturating_cast converts to a narrower type by clamping instead of truncating
    std::cout << "saturating_cast<std::uint64_t>(max) = "
              << boost::int256::saturating_cast<std::uint64_t>(max_value)
              << " (saturates to UINT64_MAX)" << std::endl;
    std::cout << "saturating_cast<std::int64_t>(max)  = "
              << boost::int256::saturating_cast<std::int64_t>(max_value)
              << " (saturates to INT64_MAX)" << std::endl;
    std::cout << "saturating_cast<std::int32_t>(max)  = "
              << boost::int256::saturating_cast<std::int32_t>(max_value)
              << " (saturates to INT32_MAX)" << std::endl;
    std::cout << "saturating_cast<std::uint64_t>(42)  = "
              << boost::int256::saturating_cast<std::uint64_t>(uint256{42U})
              << " (fits, so it is exact)" << std::endl;

    std::cout << "\n=== Saturating Arithmetic with int256 ===" << std::endl;

    using boost::int256::int256;

    constexpr auto int_max {(std::numeric_limits<int256>::max)()};
    constexpr auto int_min {(std::numeric_limits<int256>::min)()};

    std::cout << "int256 max = " << int_max << std::endl;
    std::cout << "int256 min = " << int_min << std::endl;

    // Saturating arithmetic is especially useful for a signed type, since the
    // operators wrap silently rather than being undefined the way a built-in
    // signed integer's overflow is.
    std::cout << "saturating_add(int_max, 1) = " << boost::int256::saturating_add(int_max, int256{1})
              << " (saturates to int_max; the operator wraps to int_min)" << std::endl;
    std::cout << "saturating_sub(int_min, 1) = " << boost::int256::saturating_sub(int_min, int256{1})
              << " (saturates to int_min)" << std::endl;

    std::cout << "saturating_mul(int_max, 2) = " << boost::int256::saturating_mul(int_max, int256{2})
              << " (saturates to int_max)" << std::endl;
    std::cout << "saturating_mul(-(int_max - 2), 5) = "
              << boost::int256::saturating_mul(-(int_max - int256{2}), int256{5})
              << " (saturates to int_min)" << std::endl;

    // The only case in the library where saturating_div overflows is x = MIN
    // and y = -1, whose true (unsaturated) quotient wraps back to MIN.
    std::cout << "saturating_div(int_min, -1) = " << boost::int256::saturating_div(int_min, int256{-1})
              << " (saturates to int_max)" << std::endl;

    std::cout << "saturating_cast<int256>(uint256 max) = "
              << boost::int256::saturating_cast<int256>(max_value)
              << " (saturates to int_max)" << std::endl;
    std::cout << "saturating_cast<uint256>(int256 min) = "
              << boost::int256::saturating_cast<uint256>(int_min)
              << " (clamps a negative source to 0)" << std::endl;
    std::cout << "saturating_cast<std::int64_t>(int_max) = "
              << boost::int256::saturating_cast<std::int64_t>(int_max)
              << " (saturates to INT64_MAX)" << std::endl;

    return 0;
}

Output:

=== Saturating Arithmetic ===
uint256 max = 115792089237316195423570985008687907853269984665640564039457584007913129639935

=== Saturating Addition and Subtraction ===
saturating_add(max, max) = 115792089237316195423570985008687907853269984665640564039457584007913129639935
  (saturates to max; max + max would wrap to max - 1)
saturating_sub(0, max)   = 0
  (saturates to 0; 0 - max would wrap to 1)

=== Saturating Multiplication ===
saturating_mul(2^128, 2^128) = 115792089237316195423570985008687907853269984665640564039457584007913129639935
  (saturates to max; the operator wraps to 0)
saturating_mul(max, 2)       = 115792089237316195423570985008687907853269984665640564039457584007913129639935
saturating_mul(2^100, 2^100) = 1606938044258990275541962092341162602522202993782792835301376
  (fits exactly, so nothing saturates)

=== Saturating Division ===
saturating_div(max, 3) = 38597363079105398474523661669562635951089994888546854679819194669304376546645

=== Saturating Casts ===
saturating_cast<std::uint64_t>(max) = 18446744073709551615 (saturates to UINT64_MAX)
saturating_cast<std::int64_t>(max)  = 9223372036854775807 (saturates to INT64_MAX)
saturating_cast<std::int32_t>(max)  = 2147483647 (saturates to INT32_MAX)
saturating_cast<std::uint64_t>(42)  = 42 (fits, so it is exact)

=== Saturating Arithmetic with int256 ===
int256 max = 57896044618658097711785492504343953926634992332820282019728792003956564819967
int256 min = -57896044618658097711785492504343953926634992332820282019728792003956564819968
saturating_add(int_max, 1) = 57896044618658097711785492504343953926634992332820282019728792003956564819967 (saturates to int_max; the operator wraps to int_min)
saturating_sub(int_min, 1) = -57896044618658097711785492504343953926634992332820282019728792003956564819968 (saturates to int_min)
saturating_mul(int_max, 2) = 57896044618658097711785492504343953926634992332820282019728792003956564819967 (saturates to int_max)
saturating_mul(-(int_max - 2), 5) = -57896044618658097711785492504343953926634992332820282019728792003956564819968 (saturates to int_min)
saturating_div(int_min, -1) = 57896044618658097711785492504343953926634992332820282019728792003956564819967 (saturates to int_max)
saturating_cast<int256>(uint256 max) = 57896044618658097711785492504343953926634992332820282019728792003956564819967 (saturates to int_max)
saturating_cast<uint256>(int256 min) = 0 (clamps a negative source to 0)
saturating_cast<std::int64_t>(int_max) = 9223372036854775807 (saturates to INT64_MAX)

<numeric> support (Numeric Algorithms)

Example 8. This example demonstrates gcd, lcm, and the overflow-free midpoint, ending with a binary search over the whole 256-bit range.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/numeric.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>
#include <iostream>
#include <limits>

int main()
{
    using boost::int256::uint256;

    std::cout << "=== Greatest Common Divisor (gcd) ===" << std::endl;

    // Basic gcd
    constexpr uint256 a {48U};
    constexpr uint256 b {18U};
    std::cout << "gcd(" << a << ", " << b << ") = " << boost::int256::gcd(a, b) << std::endl;

    // gcd with larger values
    constexpr uint256 large_a {123456789012345678ULL};
    constexpr uint256 large_b {987654321098765432ULL};
    std::cout << "gcd(" << large_a << ", " << large_b << ") = "
              << boost::int256::gcd(large_a, large_b) << std::endl;

    // gcd of values that need all four words
    constexpr uint256 huge_a {uint256{1U} << 200U};
    constexpr uint256 huge_b {uint256{1U} << 160U};
    std::cout << "gcd(2^200, 2^160) = " << boost::int256::gcd(huge_a, huge_b) << " (= 2^160)" << std::endl;

    // A 256-bit gcd of two products with a known common factor
    constexpr uint256 factor {uint256{1U} << 100U};
    std::cout << "gcd(3 * 2^100, 5 * 2^100) = " << boost::int256::gcd(3U * factor, 5U * factor)
              << " (= 2^100)" << std::endl;

    std::cout << "\n=== Least Common Multiple (lcm) ===" << std::endl;

    // Basic lcm
    constexpr uint256 x {12U};
    constexpr uint256 y {18U};
    std::cout << "lcm(" << x << ", " << y << ") = " << boost::int256::lcm(x, y) << std::endl;

    // lcm with coprime numbers
    constexpr uint256 p {7U};
    constexpr uint256 q {11U};
    std::cout << "lcm(" << p << ", " << q << ") = " << boost::int256::lcm(p, q)
              << " (coprime: lcm = p * q)" << std::endl;

    // The lcm of two 128-bit primes needs 256 bits, which is why the wide type helps
    constexpr uint256 prime_a {UINT64_C(18446744073709551557)};  // The largest 64-bit prime
    constexpr uint256 prime_b {UINT64_C(18446744073709551533)};  // The next one down
    std::cout << "lcm(largest two 64-bit primes) = " << boost::int256::lcm(prime_a, prime_b) << std::endl;

    // Relationship: gcd(a,b) * lcm(a,b) = a * b
    std::cout << "\nVerifying gcd * lcm = a * b:" << std::endl;
    const auto g {boost::int256::gcd(x, y)};
    const auto l {boost::int256::lcm(x, y)};
    std::cout << "gcd(" << x << ", " << y << ") * lcm(" << x << ", " << y << ") = " << (g * l) << std::endl;
    std::cout << x << " * " << y << " = " << (x * y) << std::endl;

    std::cout << "\n=== Midpoint ===" << std::endl;

    constexpr uint256 low {10U};
    constexpr uint256 high {20U};
    std::cout << "midpoint(" << low << ", " << high << ") = "
              << boost::int256::midpoint(low, high) << std::endl;

    // With an odd difference the result rounds toward the first argument
    constexpr uint256 odd_low {10U};
    constexpr uint256 odd_high {21U};
    std::cout << "midpoint(" << odd_low << ", " << odd_high << ") = "
              << boost::int256::midpoint(odd_low, odd_high) << " (rounds toward first arg)" << std::endl;
    std::cout << "midpoint(" << odd_high << ", " << odd_low << ") = "
              << boost::int256::midpoint(odd_high, odd_low) << " (rounds toward first arg)" << std::endl;

    std::cout << "\n--- Overflow-safe midpoint ---" << std::endl;

    // midpoint never overflows, which (a + b) / 2 does at this magnitude
    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};
    constexpr auto max_minus_10 {max_value - 10U};
    std::cout << "midpoint(max, max - 10) = " << boost::int256::midpoint(max_value, max_minus_10) << std::endl;
    std::cout << "(max + (max - 10)) / 2  = " << ((max_value + max_minus_10) / 2U) << " (wrapped)" << std::endl;

    std::cout << "\n--- Binary search over the whole range ---" << std::endl;

    // A midpoint that cannot overflow is what lets a binary search run over the
    // full 256-bit range. This finds the integer square root of 2^200 by bisection.
    constexpr uint256 target {uint256{1U} << 200U};
    uint256 lo {0U};
    uint256 hi {max_value};
    while (hi - lo > 1U)
    {
        const auto mid {boost::int256::midpoint(lo, hi)};
        if (mid != 0U && mid <= target / mid)
        {
            lo = mid;
        }
        else
        {
            hi = mid;
        }
    }

    std::cout << "isqrt(2^200) by bisection = " << lo << " (= 2^100)" << std::endl;

    std::cout << "\n=== gcd, lcm and midpoint with int256 ===" << std::endl;

    using boost::int256::int256;

    // gcd and lcm work on the uint256 magnitudes of the operands, so the
    // result is always non-negative regardless of the operands' signs
    constexpr int256 neg_a {-48};
    constexpr int256 pos_b {18};
    std::cout << "gcd(" << neg_a << ", " << pos_b << ") = " << boost::int256::gcd(neg_a, pos_b)
              << " (always non-negative)" << std::endl;

    // gcd(MIN, 0) is the one case whose magnitude is not representable as a
    // positive int256; like unary -, it wraps rather than being undefined
    constexpr auto min_value {(std::numeric_limits<int256>::min)()};
    std::cout << "gcd(MIN, 0) = " << boost::int256::gcd(min_value, int256{0}) << " (wraps to MIN)" << std::endl;

    std::cout << "\n--- Signed midpoint ---" << std::endl;

    constexpr int256 neg {-100};
    constexpr int256 pos {100};
    std::cout << "midpoint(" << neg << ", " << pos << ") = "
              << boost::int256::midpoint(neg, pos) << std::endl;

    constexpr int256 neg2 {-100};
    constexpr int256 neg3 {-50};
    std::cout << "midpoint(" << neg2 << ", " << neg3 << ") = "
              << boost::int256::midpoint(neg2, neg3) << std::endl;

    return 0;
}

Output:

=== Greatest Common Divisor (gcd) ===
gcd(48, 18) = 6
gcd(123456789012345678, 987654321098765432) = 2
gcd(2^200, 2^160) = 1461501637330902918203684832716283019655932542976 (= 2^160)
gcd(3 * 2^100, 5 * 2^100) = 1267650600228229401496703205376 (= 2^100)

=== Least Common Multiple (lcm) ===
lcm(12, 18) = 36
lcm(7, 11) = 77 (coprime: lcm = p * q)
lcm(largest two 64-bit primes) = 340282366920938460843936948965011886881

Verifying gcd * lcm = a * b:
gcd(12, 18) * lcm(12, 18) = 216
12 * 18 = 216

=== Midpoint ===
midpoint(10, 20) = 15
midpoint(10, 21) = 15 (rounds toward first arg)
midpoint(21, 10) = 16 (rounds toward first arg)

--- Overflow-safe midpoint ---
midpoint(max, max - 10) = 115792089237316195423570985008687907853269984665640564039457584007913129639930
(max + (max - 10)) / 2  = 57896044618658097711785492504343953926634992332820282019728792003956564819962 (wrapped)

--- Binary search over the whole range ---
isqrt(2^200) by bisection = 1267650600228229401496703205376 (= 2^100)

=== gcd, lcm and midpoint with int256 ===
gcd(-48, 18) = 6 (always non-negative)
gcd(MIN, 0) = -57896044618658097711785492504343953926634992332820282019728792003956564819968 (wraps to MIN)

--- Signed midpoint ---
midpoint(-100, 100) = 0
midpoint(-100, -50) = -75

<numeric> support (Integer Division)

Example 9. This example demonstrates the P3724 division family: every rounding mode, which of them coincide for an unsigned type, both halves from one division, and ceiling division that cannot overflow.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/numeric.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>
#include <iostream>
#include <limits>


int main()
{
    using boost::int256::uint256;

    std::cout << "=== Every rounding mode on 12 / 5 ===" << std::endl;

    // The exact quotient is 2.4, so the nearest integer is 2 and no mode ties
    constexpr uint256 x {12U};
    constexpr uint256 y {5U};

    std::cout << "div_to_zero          = " << boost::int256::div_to_zero(x, y) << std::endl;
    std::cout << "div_away_zero        = " << boost::int256::div_away_zero(x, y) << std::endl;
    std::cout << "div_to_pos_inf       = " << boost::int256::div_to_pos_inf(x, y) << std::endl;
    std::cout << "div_to_neg_inf       = " << boost::int256::div_to_neg_inf(x, y) << std::endl;
    std::cout << "div_euclid           = " << boost::int256::div_euclid(x, y) << std::endl;
    std::cout << "div_ties_to_zero     = " << boost::int256::div_ties_to_zero(x, y) << std::endl;

    // For an unsigned type several of the modes necessarily agree: nothing is ever
    // negative, so rounding towards zero, towards negative infinity, and Euclidean
    // division are the same operation, and rounding away from zero is rounding
    // towards positive infinity.
    std::cout << "\n=== Which modes coincide for an unsigned type ===" << std::endl;
    std::cout << std::boolalpha;
    std::cout << "to_zero == to_neg_inf == euclid: "
              << (boost::int256::div_to_zero(x, y) == boost::int256::div_to_neg_inf(x, y) &&
                  boost::int256::div_to_zero(x, y) == boost::int256::div_euclid(x, y)) << std::endl;
    std::cout << "away_zero == to_pos_inf:         "
              << (boost::int256::div_away_zero(x, y) == boost::int256::div_to_pos_inf(x, y)) << std::endl;
    std::cout << "operator% == rem_euclid:         "
              << ((x % y) == boost::int256::rem_euclid(x, y)) << std::endl;

    std::cout << "\n=== Tie breaking on 7 / 2 ===" << std::endl;

    // The exact quotient is 3.5, so the tie-breaking rules disagree
    constexpr uint256 tie_x {7U};
    constexpr uint256 tie_y {2U};

    std::cout << "div_ties_to_zero     = " << boost::int256::div_ties_to_zero(tie_x, tie_y) << std::endl;
    std::cout << "div_ties_away_zero   = " << boost::int256::div_ties_away_zero(tie_x, tie_y) << std::endl;
    std::cout << "div_ties_to_pos_inf  = " << boost::int256::div_ties_to_pos_inf(tie_x, tie_y) << std::endl;
    std::cout << "div_ties_to_neg_inf  = " << boost::int256::div_ties_to_neg_inf(tie_x, tie_y) << std::endl;
    std::cout << "div_ties_to_odd      = " << boost::int256::div_ties_to_odd(tie_x, tie_y) << std::endl;
    std::cout << "div_ties_to_even     = " << boost::int256::div_ties_to_even(tie_x, tie_y) << std::endl;

    std::cout << "\n=== Quotient and remainder from one division ===" << std::endl;

    // Each div_rem_ function performs a single division and returns both halves
    constexpr auto truncated {boost::int256::div_rem_to_zero(x, y)};
    std::cout << "div_rem_to_zero(12, 5):     quotient = " << truncated.quotient
              << ", remainder = " << truncated.remainder << std::endl;

    // A quotient rounded up leaves a negative remainder, which is returned reduced
    // modulo 2^256 so that the identity below still holds in the type
    constexpr auto rounded_up {boost::int256::div_rem_to_pos_inf(x, y)};
    std::cout << "div_rem_to_pos_inf(12, 5):  quotient = " << rounded_up.quotient
              << ", remainder = " << rounded_up.remainder << std::endl;

    // The remainder always satisfies x == quotient * y + remainder
    std::cout << "quotient * y + remainder = " << (truncated.quotient * y + truncated.remainder)
              << " and " << (rounded_up.quotient * y + rounded_up.remainder) << std::endl;

    std::cout << "\n=== Ceiling division without overflow ===" << std::endl;

    // Counting fixed size blocks needed to cover a length is the classic use for
    // rounding towards positive infinity. The usual (length + block - 1) / block
    // overflows here, while div_to_pos_inf does not.
    constexpr auto length {(std::numeric_limits<uint256>::max)()};
    constexpr uint256 block {1000U};

    std::cout << "length = " << length << std::endl;
    std::cout << "div_to_pos_inf(length, 1000) = " << boost::int256::div_to_pos_inf(length, block) << std::endl;
    std::cout << "div_to_zero(length, 1000)    = " << boost::int256::div_to_zero(length, block) << std::endl;
    std::cout << "(length + 999) / 1000        = " << ((length + 999U) / block) << " (wrapped)" << std::endl;

    std::cout << "\n=== Unbiased rounding of a scaled value ===" << std::endl;

    // Rounding half to even keeps a long running sum from drifting upwards, which is
    // what operator/ plus a manual half-adjustment would do
    constexpr uint256 scale {1000U};
    const uint256 samples[] {uint256{1500U}, uint256{2500U}, uint256{3500U}, uint256{4500U}};

    for (const auto& sample : samples)
    {
        std::cout << sample << " / 1000: ties_to_even = " << boost::int256::div_ties_to_even(sample, scale)
                  << ", ties_away_zero = " << boost::int256::div_ties_away_zero(sample, scale) << std::endl;
    }

    std::cout << "\n=== Every rounding mode on -12 / 5, with int256 ===" << std::endl;

    using boost::int256::int256;

    // The exact quotient is -2.4, so the nearest integer is -2 and no mode ties.
    // Unlike uint256 above, every mode can now disagree with every other.
    constexpr int256 sx {-12};
    constexpr int256 sy {5};

    std::cout << "div_to_zero          = " << boost::int256::div_to_zero(sx, sy) << std::endl;
    std::cout << "div_away_zero        = " << boost::int256::div_away_zero(sx, sy) << std::endl;
    std::cout << "div_to_pos_inf       = " << boost::int256::div_to_pos_inf(sx, sy) << std::endl;
    std::cout << "div_to_neg_inf       = " << boost::int256::div_to_neg_inf(sx, sy) << std::endl;
    std::cout << "div_euclid           = " << boost::int256::div_euclid(sx, sy) << std::endl;
    std::cout << "div_ties_to_zero     = " << boost::int256::div_ties_to_zero(sx, sy) << std::endl;

    constexpr auto s_floored {boost::int256::div_rem_to_neg_inf(sx, sy)};
    std::cout << "\ndiv_rem_to_neg_inf(-12, 5): quotient = " << s_floored.quotient
              << ", remainder = " << s_floored.remainder << std::endl;

    constexpr auto s_truncated {boost::int256::div_rem_to_zero(sx, sy)};
    std::cout << "div_rem_to_zero(-12, 5):    quotient = " << s_truncated.quotient
              << ", remainder = " << s_truncated.remainder << std::endl;

    std::cout << "quotient * y + remainder = " << (s_floored.quotient * sy + s_floored.remainder) << std::endl;

    std::cout << "\n=== Euclidean remainder is never negative ===" << std::endl;

    // operator% takes its sign from the dividend, which makes it a poor fit for
    // wrapping an offset into a range. rem_euclid always lands in [0, abs(y)).
    constexpr int256 modulus {7};
    for (int256 offset {-9}; offset <= -6; ++offset)
    {
        std::cout << offset << " % 7 = " << (offset % modulus)
                  << ", rem_euclid(" << offset << ", 7) = " << boost::int256::rem_euclid(offset, modulus) << std::endl;
    }

    std::cout << "\n=== MIN / -1, the one signed overflow this library defines ===" << std::endl;

    // The paper's other division rules are unaffected: only the truncating
    // quotient overflows here, and this library wraps it back to MIN rather
    // than leaving it undefined, matching every other overflowing signed op.
    constexpr auto min_value {(std::numeric_limits<int256>::min)()};
    std::cout << "div_to_zero(MIN, -1)      = " << boost::int256::div_to_zero(min_value, int256{-1}) << std::endl;
    std::cout << "div_rem_to_zero(MIN, -1)  = quotient "
              << boost::int256::div_rem_to_zero(min_value, int256{-1}).quotient
              << ", remainder " << boost::int256::div_rem_to_zero(min_value, int256{-1}).remainder << std::endl;

    return 0;
}

Output:

=== Every rounding mode on 12 / 5 ===
div_to_zero          = 2
div_away_zero        = 3
div_to_pos_inf       = 3
div_to_neg_inf       = 2
div_euclid           = 2
div_ties_to_zero     = 2

=== Which modes coincide for an unsigned type ===
to_zero == to_neg_inf == euclid: true
away_zero == to_pos_inf:         true
operator% == rem_euclid:         true

=== Tie breaking on 7 / 2 ===
div_ties_to_zero     = 3
div_ties_away_zero   = 4
div_ties_to_pos_inf  = 4
div_ties_to_neg_inf  = 3
div_ties_to_odd      = 3
div_ties_to_even     = 4

=== Quotient and remainder from one division ===
div_rem_to_zero(12, 5):     quotient = 2, remainder = 2
div_rem_to_pos_inf(12, 5):  quotient = 3, remainder = 115792089237316195423570985008687907853269984665640564039457584007913129639933
quotient * y + remainder = 12 and 12

=== Ceiling division without overflow ===
length = 115792089237316195423570985008687907853269984665640564039457584007913129639935
div_to_pos_inf(length, 1000) = 115792089237316195423570985008687907853269984665640564039457584007913129640
div_to_zero(length, 1000)    = 115792089237316195423570985008687907853269984665640564039457584007913129639
(length + 999) / 1000        = 0 (wrapped)

=== Unbiased rounding of a scaled value ===
1500 / 1000: ties_to_even = 2, ties_away_zero = 2
2500 / 1000: ties_to_even = 2, ties_away_zero = 3
3500 / 1000: ties_to_even = 4, ties_away_zero = 4
4500 / 1000: ties_to_even = 4, ties_away_zero = 5

=== Every rounding mode on -12 / 5, with int256 ===
div_to_zero          = -2
div_away_zero        = -3
div_to_pos_inf       = -2
div_to_neg_inf       = -3
div_euclid           = -3
div_ties_to_zero     = -2

div_rem_to_neg_inf(-12, 5): quotient = -3, remainder = 3
div_rem_to_zero(-12, 5):    quotient = -2, remainder = -2
quotient * y + remainder = -12

=== Euclidean remainder is never negative ===
-9 % 7 = -2, rem_euclid(-9, 7) = 5
-8 % 7 = -1, rem_euclid(-8, 7) = 6
-7 % 7 = 0, rem_euclid(-7, 7) = 0
-6 % 7 = -6, rem_euclid(-6, 7) = 1

=== MIN / -1, the one signed overflow this library defines ===
div_to_zero(MIN, -1)      = -57896044618658097711785492504343953926634992332820282019728792003956564819968
div_rem_to_zero(MIN, -1)  = quotient -57896044618658097711785492504343953926634992332820282019728792003956564819968, remainder 0

Checked Arithmetic

Example 10. This example demonstrates the C23 ckd_add, ckd_sub and ckd_mul contract, including mixed operand and result types.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// Individual headers

#include <boost/int256/utilities.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>

// Or you can do a single header

// #include <boost/int256.hpp>

#include <cstdint>
#include <iostream>
#include <limits>

int main()
{
    using boost::int256::uint256;
    using boost::int256::int256;
    using boost::int256::ckd_add;
    using boost::int256::ckd_sub;
    using boost::int256::ckd_mul;

    std::cout << std::boolalpha;

    // ckd_add, ckd_sub, and ckd_mul implement the C23 stdckdint.h contract: the
    // operation is evaluated as if both operands had infinite range, the result
    // is written to *result wrapped to that type's width, and the function
    // returns true when the exact result did not fit.
    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};

    // A result that fits returns false and holds the exact value.
    std::cout << "=== Results That Fit ===" << std::endl;
    uint256 r {};
    bool overflow {ckd_add(&r, uint256{20U}, uint256{22U})};
    std::cout << "ckd_add(20, 22): overflow=" << overflow << ", result=" << r << std::endl;

    // Addition that exceeds the type wraps modulo 2^256 and reports overflow.
    std::cout << "\n=== Addition Overflow ===" << std::endl;
    overflow = ckd_add(&r, max_value, uint256{1U});
    std::cout << "ckd_add(max, 1): overflow=" << overflow << ", wrapped=" << r << std::endl;

    // Subtracting below zero in an unsigned type wraps to the top of the range.
    std::cout << "\n=== Subtraction Underflow ===" << std::endl;
    overflow = ckd_sub(&r, uint256{0U}, uint256{1U});
    std::cout << "ckd_sub(0, 1): overflow=" << overflow << ", wrapped=" << r << std::endl;

    // Multiplication detects the overflow that operator* silently wraps. This is the
    // case a 256-bit type runs into most often: the square of anything at or above
    // 2^128 does not fit.
    std::cout << "\n=== Multiplication Overflow ===" << std::endl;
    constexpr uint256 two_to_128 {uint256{1U} << 128U};
    overflow = ckd_mul(&r, two_to_128, two_to_128);
    std::cout << "ckd_mul(2^128, 2^128): overflow=" << overflow << ", wrapped=" << r << std::endl;

    overflow = ckd_mul(&r, two_to_128 - 1U, two_to_128 - 1U);
    std::cout << "ckd_mul(2^128 - 1, 2^128 - 1): overflow=" << overflow << ", result=" << r
              << "\n  (the largest product that still fits)" << std::endl;

    // The result type and the two operand types are independent: they may differ
    // in width and signedness, and the exact mathematical value is always used.
    std::cout << "\n=== Mixed Types ===" << std::endl;
    std::uint64_t narrow {};
    overflow = ckd_add(&narrow, uint256{5U}, 3);
    std::cout << "ckd_add<uint64_t>(uint256{5}, 3): overflow=" << overflow
              << ", result=" << narrow << std::endl;

    overflow = ckd_add(&narrow, max_value, uint256{0U});
    std::cout << "ckd_add<uint64_t>(max, 0): overflow=" << overflow
              << ", wrapped=" << narrow << std::endl;

    // A wide result from narrow operands cannot overflow, which is a convenient way
    // to get the exact product of two 128-bit values
    overflow = ckd_mul(&r, UINT64_MAX, UINT64_MAX);
    std::cout << "ckd_mul<uint256>(UINT64_MAX, UINT64_MAX): overflow=" << overflow
              << ", result=" << r << std::endl;

    // Narrow targets make the wrap-around easy to see (400 modulo 256 is 144).
    std::uint8_t byte {};
    overflow = ckd_mul(&byte, std::uint8_t{20}, std::uint8_t{20});
    std::cout << "ckd_mul<uint8_t>(20, 20): overflow=" << overflow
              << ", wrapped=" << static_cast<int>(byte) << std::endl;

    std::cout << "\n=== Checked Arithmetic with int256 ===" << std::endl;

    constexpr auto int_max {(std::numeric_limits<int256>::max)()};
    constexpr auto int_min {(std::numeric_limits<int256>::min)()};

    int256 sr {};
    overflow = ckd_add(&sr, int_max, int256{2});
    std::cout << "ckd_add(INT256_MAX, 2): overflow=" << overflow << ", wrapped=" << sr << std::endl;

    // Multiplication detects overflow that operator* would silently roll over,
    // including MIN * -1, whose true result is not representable.
    overflow = ckd_mul(&sr, int_max, int256{2});
    std::cout << "ckd_mul(INT256_MAX, 2): overflow=" << overflow << ", wrapped=" << sr << std::endl;
    overflow = ckd_mul(&sr, int_min, int256{-1});
    std::cout << "ckd_mul(INT256_MIN, -1): overflow=" << overflow << ", wrapped=" << sr << std::endl;

    // The result type and the two operand types are independent, and may
    // differ in signedness; the exact mathematical value is always used.
    std::int64_t narrow_signed {};
    overflow = ckd_add(&narrow_signed, uint256{5U}, int256{-3});
    std::cout << "ckd_add<int64_t>(uint256{5}, int256{-3}): overflow=" << overflow
              << ", result=" << narrow_signed << std::endl;

    return 0;
}

Output:

=== Results That Fit ===
ckd_add(20, 22): overflow=false, result=42

=== Addition Overflow ===
ckd_add(max, 1): overflow=true, wrapped=0

=== Subtraction Underflow ===
ckd_sub(0, 1): overflow=true, wrapped=115792089237316195423570985008687907853269984665640564039457584007913129639935

=== Multiplication Overflow ===
ckd_mul(2^128, 2^128): overflow=true, wrapped=0
ckd_mul(2^128 - 1, 2^128 - 1): overflow=false, result=115792089237316195423570985008687907852589419931798687112530834793049593217025
  (the largest product that still fits)

=== Mixed Types ===
ckd_add<uint64_t>(uint256{5}, 3): overflow=false, result=8
ckd_add<uint64_t>(max, 0): overflow=true, wrapped=18446744073709551615
ckd_mul<uint256>(UINT64_MAX, UINT64_MAX): overflow=false, result=340282366920938463426481119284349108225
ckd_mul<uint8_t>(20, 20): overflow=true, wrapped=144

=== Checked Arithmetic with int256 ===
ckd_add(INT256_MAX, 2): overflow=true, wrapped=-57896044618658097711785492504343953926634992332820282019728792003956564819967
ckd_mul(INT256_MAX, 2): overflow=true, wrapped=-2
ckd_mul(INT256_MIN, -1): overflow=true, wrapped=-57896044618658097711785492504343953926634992332820282019728792003956564819968
ckd_add<int64_t>(uint256{5}, int256{-3}): overflow=false, result=2

Mixed Signedness Arithmetic

Example 11. This example demonstrates arithmetic between uint256 and the built-in integer types under the usual arithmetic conversions, including compound assignment onto a built-in integer, and the corresponding rules for int256 against a builtin and against uint256 itself.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256.hpp>
#include <cstdint>
#include <iostream>

int main()
{
    // Arithmetic and comparison between uint256 and the built-in integer types
    // follow the C++ usual arithmetic conversions, identical to what the built-in
    // unsigned __int128 does with a narrower operand.

    using boost::int256::uint256;

    std::cout << "=== Arithmetic with built-in integers ===" << std::endl;

    constexpr uint256 value {3U};
    std::cout << "value = " << value << std::endl;

    std::cout << "value + 1  = " << (value + 1) << std::endl;
    std::cout << "value - 1  = " << (value - 1) << std::endl;
    std::cout << "value * 2  = " << (value * 2) << std::endl;
    std::cout << "value / 3  = " << (value / 3) << std::endl;
    std::cout << "value % 3  = " << (value % 3) << std::endl;
    std::cout << "10 - value = " << (10 - value) << std::endl;
    std::cout << "10 / value = " << (10 / value) << std::endl;

    std::cout << "\n=== A signed operand is converted, not compared as signed ===" << std::endl;

    // A negative operand becomes the unsigned value it represents modulo 2^256, so
    // subtracting a negative adds, and a comparison against a negative is false. This
    // is exactly what a built-in unsigned type does with a negative operand.
    std::cout << std::boolalpha;
    std::cout << "value - (-1)   = " << (value - -1) << std::endl;
    std::cout << "value > -1     = " << (value > -1) << " (the -1 becomes 2^256 - 1)" << std::endl;
    std::cout << "uint256{0} - 1 = " << (uint256{0U} - 1) << std::endl;
    std::cout << "(use cmp_greater from <boost/int256/utilities.hpp> for the mathematical answer)"
              << std::endl;

    std::cout << "\n=== The result type is always the wider one ===" << std::endl;

    // A 256-bit operand on either side means the operation happens in 256 bits, so
    // a product that would overflow the narrow type does not overflow here
    const std::uint64_t narrow {UINT64_MAX};
    std::cout << "UINT64_MAX * uint256{UINT64_MAX} = " << (narrow * uint256{narrow}) << std::endl;
    std::cout << "UINT64_MAX * UINT64_MAX in 64 bits = " << (narrow * narrow) << " (wrapped)" << std::endl;

    std::cout << "\n=== Compound Assignment onto a Built-in Integer ===" << std::endl;

    // A built-in integer accepts a uint256 right operand for every compound assignment.
    // The operation happens in the common type and the result is converted back, so the
    // left operand keeps its own type exactly as it would with a built-in __int128
    unsigned flags {0};
    flags |= uint256{1U};
    std::cout << "unsigned{0} |= uint256{1} -> " << flags << std::endl;

    int counter {12};
    counter += uint256{5U};
    std::cout << "int{12} += uint256{5} -> " << counter << std::endl;

    // A right operand wider than the left operand is not truncated before the operation:
    // the sum is formed in 256 bits and only the result is narrowed
    std::uint32_t word {0};
    word += (uint256{1U} << 64U) + uint256{7U};
    std::cout << "uint32_t{0} += 2^64 + 7 -> " << word << std::endl;

    // A shift takes only its count from the 256-bit operand, so the value shifted and the
    // type of the result are those of the left operand
    std::uint64_t bits {1};
    bits <<= uint256{40U};
    std::cout << "uint64_t{1} <<= uint256{40} -> " << bits << std::endl;

    std::cout << "\n=== Conversions ===" << std::endl;

    // The conversion to a narrower integer keeps the low bits, like a static_cast
    // between built-in integers
    const uint256 wide {(uint256{1U} << 128U) + 12345U};
    std::cout << "static_cast<std::uint64_t>(2^128 + 12345) = "
              << static_cast<std::uint64_t>(wide) << std::endl;

    // operator bool is explicit, so a uint256 works in a condition but not in arithmetic
    std::cout << "static_cast<bool>(uint256{0}) = " << static_cast<bool>(uint256{0U}) << std::endl;
    std::cout << "static_cast<bool>(wide)       = " << static_cast<bool>(wide) << std::endl;

    std::cout << "\n=== Mixed Type Arithmetic with int256 ===" << std::endl;

    using boost::int256::int256;

    constexpr int256 signed_value {-3};
    std::cout << "signed_value = " << signed_value << std::endl;

    // Every built-in integer fits inside int256's range, so the operation is
    // always mathematically exact: there is no mixed sign trap here the way
    // there is between uint256 and a negative builtin above.
    std::cout << "signed_value + 1U = " << (signed_value + 1U) << std::endl;
    std::cout << "signed_value - 4U = " << (signed_value - 4U) << std::endl;
    std::cout << "signed_value * 2  = " << (signed_value * 2) << std::endl;
    std::cout << "signed_value / 4U = " << (signed_value / 4U) << std::endl;

    std::cout << "\n=== int256 and uint256 Follow the Same Rule as uint256 and a Builtin ===" << std::endl;

    // int256 and uint256 use the usual arithmetic conversions with each other
    // too: both become uint256, so the same "converted, not compared as
    // signed" trap from above applies between the two library types as well.
    std::cout << "int256{-1} < uint256{1}   = " << (int256{-1} < uint256{1U})
              << " (the -1 becomes uint256 max)" << std::endl;
    std::cout << "int256{-1} + uint256{1}   = " << (int256{-1} + uint256{1U})
              << " (result type is uint256)" << std::endl;

    std::cout << "\n=== Compound Assignment Between int256 and uint256 ===" << std::endl;

    // int256's ten operator@=(int256&, const uint256&) overloads are the one
    // place this library computes a compound assignment in a domain other
    // than *this's own type: dividing or shifting by a uint256 happens
    // entirely in the uint256 domain, and only the final result converts
    // back, matching what long long /= unsigned long long does at 64 bits.
    // (Boost.Int128's int128 /= uint128 has no such overload and divides
    // signed instead, which this library does not repeat.)
    int256 dividend {-10};
    dividend /= uint256{3U};
    std::cout << "int256{-10} /= uint256{3} -> " << dividend
              << " ((uint256)-10 / 3, converted back)" << std::endl;

    return 0;
}

Output:

=== Arithmetic with built-in integers ===
value = 3
value + 1  = 4
value - 1  = 2
value * 2  = 6
value / 3  = 1
value % 3  = 0
10 - value = 7
10 / value = 3

=== A signed operand is converted, not compared as signed ===
value - (-1)   = 4
value > -1     = false (the -1 becomes 2^256 - 1)
uint256{0} - 1 = 115792089237316195423570985008687907853269984665640564039457584007913129639935
(use cmp_greater from <boost/int256/utilities.hpp> for the mathematical answer)

=== The result type is always the wider one ===
UINT64_MAX * uint256{UINT64_MAX} = 340282366920938463426481119284349108225
UINT64_MAX * UINT64_MAX in 64 bits = 1 (wrapped)

=== Compound Assignment onto a Built-in Integer ===
unsigned{0} |= uint256{1} -> 1
int{12} += uint256{5} -> 17
uint32_t{0} += 2^64 + 7 -> 7
uint64_t{1} <<= uint256{40} -> 1099511627776

=== Conversions ===
static_cast<std::uint64_t>(2^128 + 12345) = 12345
static_cast<bool>(uint256{0}) = false
static_cast<bool>(wide)       = true

=== Mixed Type Arithmetic with int256 ===
signed_value = -3
signed_value + 1U = -2
signed_value - 4U = -7
signed_value * 2  = -6
signed_value / 4U = 0

=== int256 and uint256 Follow the Same Rule as uint256 and a Builtin ===
int256{-1} < uint256{1}   = false (the -1 becomes uint256 max)
int256{-1} + uint256{1}   = 0 (result type is uint256)

=== Compound Assignment Between int256 and uint256 ===
int256{-10} /= uint256{3} -> 38597363079105398474523661669562635951089994888546854679819194669304376546642 ((uint256)-10 / 3, converted back)

Mixed Floating-Point Arithmetic

Example 12. This example demonstrates arithmetic and comparison against float and double, which convert the 256-bit operand first, exactly as the built-in types do, for both uint256 and int256.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256.hpp>
#include <iomanip>
#include <iostream>
#include <limits>

int main()
{
    // Operations between uint256 and the built-in floating point types follow the C++
    // usual arithmetic conversions, identical to the built-in unsigned __int128: the
    // 256-bit operand is converted to the floating point type first, and the result is
    // that floating point type.

    using boost::int256::uint256;

    std::cout << "=== Arithmetic ===" << std::endl;

    constexpr uint256 value {5U};

    std::cout << "value           = " << value << std::endl;
    std::cout << "value + 1.0     = " << (value + 1.0) << std::endl;
    std::cout << "1.0 - value     = " << (1.0 - value) << std::endl;

    // The floating point operand is not truncated first, so this is 2.5 and not 0
    std::cout << "value * 0.5     = " << (value * 0.5) << std::endl;
    std::cout << "value / 2.0     = " << (value / 2.0) << std::endl;
    std::cout << "value * 1.5F    = " << (value * 1.5F) << " (a float operand gives a float)" << std::endl;

    std::cout << "\n=== Comparisons ===" << std::endl;

    std::cout << std::boolalpha;
    std::cout << "value < 5.5     = " << (value < 5.5) << std::endl;
    std::cout << "value == 5.0    = " << (value == 5.0) << std::endl;
    std::cout << "value > 4.999   = " << (value > 4.999) << std::endl;

    std::cout << "\n=== Compound Assignment ===" << std::endl;

    // The value is converted to double, the operation is applied, and the result is
    // converted back, truncating toward zero
    uint256 accumulator {4U};
    accumulator *= 2.5;
    std::cout << "uint256{4} *= 2.5         = " << accumulator << std::endl;

    // A floating point left operand keeps its own type
    double total {1.0};
    total += uint256{2U};
    std::cout << "double{1.0} += uint256{2} = " << total << std::endl;

    std::cout << "\n=== Conversions to floating point ===" << std::endl;

    // The conversion is correctly rounded, to nearest with ties to even, on every
    // platform and in both builtin configurations
    std::cout << std::setprecision(17);
    std::cout << "double(2^200)  = " << static_cast<double>(uint256{1U} << 200U) << std::endl;
    std::cout << "double(max)    = " << static_cast<double>(~uint256{}) << std::endl;
    // A float tops out around 3.4e38, so 2^200 is above its range and becomes infinity
    std::cout << "float(2^200)   = " << static_cast<float>(uint256{1U} << 200U)
              << " (above the range of a float)" << std::endl;
    std::cout << "float(2^100)   = " << static_cast<float>(uint256{1U} << 100U) << std::endl;

    // A double has 53 significand bits, so anything wider is rounded. The max rounds up
    // to exactly 2^256, which is the first double above the range of the type. The
    // comparison below is the library's mixed operator, which converts the 256-bit
    // operand to double first.
    std::cout << "max == 2^256 as a double: " << (~uint256{} == 1.157920892373162e77) << std::endl;

    std::cout << "\n=== Precision ===" << std::endl;

    // A comparison converts the 256-bit value to double first, so it is only as precise
    // as a double. This is what the built-in types do as well.
    constexpr uint256 two_200 {uint256{1U} << 200U};
    constexpr uint256 two_200_plus_one {two_200 + uint256{1U}};

    std::cout << "2^200 + 1                  = " << two_200_plus_one << std::endl;
    std::cout << "2^200 + 1 == double(2^200) = "
              << (two_200_plus_one == 1.6069380442589903e60) << std::endl;
    std::cout << "2^200 + 1 == 2^200 exactly = " << (two_200_plus_one == two_200) << std::endl;

    std::cout << "\n=== Arithmetic with int256 ===" << std::endl;

    using boost::int256::int256;

    constexpr int256 signed_value {-5};

    std::cout << "signed_value       = " << signed_value << std::endl;
    std::cout << "signed_value * 1.5 = " << (signed_value * 1.5) << std::endl;
    std::cout << "2.0 + signed_value = " << (2.0 + signed_value) << std::endl;

    std::cout << "\n=== Comparisons with int256 ===" << std::endl;

    std::cout << "signed_value < 0.0 = " << (signed_value < 0.0) << std::endl;

    std::cout << "\n=== Conversions to floating point with int256 ===" << std::endl;

    // The conversion is symmetric: converting f and -f (for a finite, non-zero
    // f) gives values that are exact negatives of each other, and it goes
    // through the same magnitude-then-sign path as the constructor.
    constexpr auto min_value {(std::numeric_limits<int256>::min)()};
    std::cout << "double(MIN) = " << static_cast<double>(min_value) << std::endl;
    std::cout << "double(MAX) = " << static_cast<double>((std::numeric_limits<int256>::max)()) << std::endl;
    std::cout << "double(-2^200) = " << static_cast<double>(-(int256{1} << 200)) << std::endl;

    return 0;
}

Output:

=== Arithmetic ===
value           = 5
value + 1.0     = 6
1.0 - value     = -4
value * 0.5     = 2.5
value / 2.0     = 2.5
value * 1.5F    = 7.5 (a float operand gives a float)

=== Comparisons ===
value < 5.5     = true
value == 5.0    = true
value > 4.999   = true

=== Compound Assignment ===
uint256{4} *= 2.5         = 10
double{1.0} += uint256{2} = 3

=== Conversions to floating point ===
double(2^200)  = 1.6069380442589903e+60
double(max)    = 1.157920892373162e+77
float(2^200)   = inf (above the range of a float)
float(2^100)   = 1.2676506002282294e+30
max == 2^256 as a double: true

=== Precision ===
2^200 + 1                  = 1606938044258990275541962092341162602522202993782792835301377
2^200 + 1 == double(2^200) = true
2^200 + 1 == 2^200 exactly = false

=== Arithmetic with int256 ===
signed_value       = -5
signed_value * 1.5 = -7.5
2.0 + signed_value = -3

=== Comparisons with int256 ===
signed_value < 0.0 = true

=== Conversions to floating point with int256 ===
double(MIN) = -5.7896044618658098e+76
double(MAX) = 5.7896044618658098e+76
double(-2^200) = -1.6069380442589903e+60

Boost.Int128 Interoperability

Example 13. This example demonstrates construction, conversion, mixed arithmetic, compound assignment, shifts, and the numeric utilities between the 256-bit types and Boost.Int128’s uint128 and int128, which are enabled by including <boost/int128.hpp> before <boost/int256.hpp>.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// Including Boost.Int128 before Boost.Int256 is what enables the interoperability:
// Boost.Int256 never includes <boost/int128.hpp> on its own
#include <boost/int128.hpp>
#include <boost/int256.hpp>
#include <iostream>
#include <limits>

int main()
{
    using boost::int128::uint128;
    using boost::int128::int128;
    using boost::int256::uint256;
    using boost::int256::int256;

    std::cout << std::boolalpha;
    std::cout << "=== Construction and Conversion ===" << std::endl;

    // Construction from uint128 and int128 is implicit and exact, and a negative
    // int128 sign fills the upper words exactly as a negative __int128 does
    constexpr uint128 u128_max {(std::numeric_limits<uint128>::max)()};
    const uint256 from_unsigned {u128_max};
    const int256 from_signed {int128{-42}};
    const uint256 sign_filled {int128{-1}};
    std::cout << "uint256{uint128 max}   = " << from_unsigned << std::endl;
    std::cout << "int256{int128{-42}}    = " << from_signed << std::endl;
    std::cout << "uint256{int128{-1}} is uint256 max: "
              << (sign_filled == (std::numeric_limits<uint256>::max)()) << std::endl;

    // The conversion back is implicit too and keeps the low 128 bits. Copy initialization
    // is the portable spelling: see the documentation for static_cast
    const uint256 wide {(uint256{1U} << 128U) + 7U};
    const uint128 low_bits = wide;
    const int128 negative = int256{-5};
    std::cout << "uint128 from 2^128 + 7 = " << low_bits << std::endl;
    std::cout << "int128 from int256{-5} = " << negative << std::endl;

    std::cout << "\n=== Mixed Arithmetic ===" << std::endl;

    // The result has the 256-bit type, so a product that overflows 128 bits is exact here
    std::cout << "uint128 max * uint256{uint128 max} = " << (u128_max * uint256{u128_max}) << std::endl;
    std::cout << "int256{-10} / int128{3}   = " << (int256{-10} / int128{3}) << std::endl;
    std::cout << "int128{-10} % int256{3}   = " << (int128{-10} % int256{3}) << std::endl;

    // The usual arithmetic conversions apply, exactly as with the builtin 128-bit types:
    // against uint256 a negative int128 becomes a huge unsigned value
    std::cout << "uint256{0} < int128{-1}   = " << (uint256{0U} < int128{-1}) << std::endl;
    std::cout << "int256{0} > int128{-1}    = " << (int256{0} > int128{-1}) << std::endl;

    std::cout << "\n=== Compound Assignment and Shifts ===" << std::endl;

    int256 total {100};
    total -= int128{142};
    total *= uint128{3U};
    std::cout << "int256{100} -= 142, *= 3  = " << total << std::endl;

    // With the Boost.Int128 type on the left, the operation happens at 256 bits and only
    // the result is narrowed, so dividing by 2^200 gives 0 rather than dividing by zero
    uint128 counter {10U};
    counter += uint256{5U};
    std::cout << "uint128{10} += uint256{5} = " << counter << std::endl;
    counter /= uint256{1U} << 200U;
    std::cout << "uint128{15} /= 2^200      = " << counter << std::endl;

    // A shift takes its value and its result type from the left operand
    std::cout << "uint128{1} << uint256{100} = " << (uint128{1U} << uint256{100U}) << std::endl;
    std::cout << "int256{-256} >> int128{4}  = " << (int256{-256} >> int128{4}) << std::endl;

    std::cout << "\n=== Numeric Utilities ===" << std::endl;

    std::cout << "saturating_cast<uint128>(2^200)   = "
              << boost::int256::saturating_cast<uint128>(uint256{1U} << 200U) << std::endl;
    std::cout << "saturating_cast<int128>(int256 min) = "
              << boost::int256::saturating_cast<int128>((std::numeric_limits<int256>::min)()) << std::endl;

    // Boost.Int128 has its own ckd_* templates, so qualify these calls to pick this library's
    uint128 sum {};
    const bool overflowed {boost::int256::ckd_add(&sum, u128_max, uint256{1U})};
    std::cout << "ckd_add(uint128 max, 1) overflowed: " << overflowed << ", wrapped sum = " << sum << std::endl;

    std::cout << "cmp_less(int128{-1}, uint256{0})  = " << boost::int256::cmp_less(int128{-1}, uint256{0U}) << std::endl;
    std::cout << "in_range<int128>(uint256{2^127})  = " << boost::int256::in_range<int128>(uint256{1U} << 127U) << std::endl;

    return 0;
}

Output:

=== Construction and Conversion ===
uint256{uint128 max}   = 340282366920938463463374607431768211455
int256{int128{-42}}    = -42
uint256{int128{-1}} is uint256 max: true
uint128 from 2^128 + 7 = 7
int128 from int256{-5} = -5

=== Mixed Arithmetic ===
uint128 max * uint256{uint128 max} = 115792089237316195423570985008687907852589419931798687112530834793049593217025
int256{-10} / int128{3}   = -3
int128{-10} % int256{3}   = -1
uint256{0} < int128{-1}   = true
int256{0} > int128{-1}    = true

=== Compound Assignment and Shifts ===
int256{100} -= 142, *= 3  = -126
uint128{10} += uint256{5} = 15
uint128{15} /= 2^200      = 0
uint128{1} << uint256{100} = 1267650600228229401496703205376
int256{-256} >> int128{4}  = -16

=== Numeric Utilities ===
saturating_cast<uint128>(2^200)   = 340282366920938463463374607431768211455
saturating_cast<int128>(int256 min) = -170141183460469231731687303715884105728
ckd_add(uint128 max, 1) overflowed: true, wrapped sum = 0
cmp_less(int128{-1}, uint256{0})  = true
in_range<int128>(uint256{2^127})  = false

Boost Math and Random Integration

Example 14. This example demonstrates generating uint256 and int256 values with a Boost.Random distribution, both over the whole range and over a narrow range.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256.hpp>
#include <boost/int256/random.hpp> // Not included in the convenience header, but needed for boost.random interop

#include <boost/random/uniform_int_distribution.hpp>
#include <array>
#include <iostream>
#include <limits>
#include <random>

int main()
{
    using boost::int256::uint256;

    std::cout << "=== Random uint256 values ===" << std::endl;

    // Setup our rng and distribution. <boost/int256/random.hpp> provides the traits
    // that Boost.Random needs to treat uint256 as an integer type.
    std::mt19937_64 rng {42};
    boost::random::uniform_int_distribution<uint256> dist {0, (std::numeric_limits<uint256>::max)()};

    std::cout << "Three draws over the whole range:" << std::endl;
    for (int i {0}; i < 3; ++i)
    {
        std::cout << "  " << dist(rng) << std::endl;
    }

    // A distribution over a narrow range at the top of the type works the same way
    std::cout << "\nThree draws from [2^200, 2^200 + 1000]:" << std::endl;
    const uint256 low {uint256{1U} << 200U};
    boost::random::uniform_int_distribution<uint256> narrow {low, low + 1000U};
    for (int i {0}; i < 3; ++i)
    {
        const auto draw {narrow(rng)};
        std::cout << "  " << draw << " (offset " << (draw - low) << ")" << std::endl;
    }

    std::cout << "\n=== A random data set ===" << std::endl;

    // Create a data set of random uint256 values using the dist and rng from above
    std::array<uint256, 10000> data_set {};
    for (auto& value : data_set)
    {
        value = dist(rng);
    }

    // Nothing here needs a wider accumulator: the mean of the top words is enough to
    // show the draws are spread over the whole range. Summing the values themselves
    // would wrap, so the average is taken over the most significant word.
    long double top_word_total {0.0L};
    uint256 minimum {(std::numeric_limits<uint256>::max)()};
    uint256 maximum {0U};

    for (const auto& value : data_set)
    {
        top_word_total += static_cast<long double>(value.words[3]);
        minimum = value < minimum ? value : minimum;
        maximum = value > maximum ? value : maximum;
    }

    const auto expected_mean {static_cast<long double>(UINT64_MAX) / 2.0L};
    const auto actual_mean {top_word_total / static_cast<long double>(data_set.size())};

    std::cout << "Samples:               " << data_set.size() << std::endl;
    std::cout << "Mean of the top word:  " << actual_mean << std::endl;
    std::cout << "Expected (UINT64_MAX/2): " << expected_mean << std::endl;
    std::cout << "Within 2 percent:      " << std::boolalpha
              << (actual_mean > expected_mean * 0.98L && actual_mean < expected_mean * 1.02L) << std::endl;
    std::cout << "Smallest draw has a nonzero top word: " << (minimum.words[3] != 0U) << std::endl;
    std::cout << "Largest draw is above 2^255:          " << (maximum > (uint256{1U} << 255U)) << std::endl;

    std::cout << "\n=== Random int256 values ===" << std::endl;

    using boost::int256::int256;

    // random.hpp's traits mark int256 as signed too, so Boost.Random's
    // uniform_int_distribution draws from the full [MIN, MAX] range
    boost::random::uniform_int_distribution<int256> signed_dist {
        (std::numeric_limits<int256>::min)(), (std::numeric_limits<int256>::max)()};

    std::cout << "Three signed draws over the whole range:" << std::endl;
    for (int i {0}; i < 3; ++i)
    {
        std::cout << "  " << signed_dist(rng) << std::endl;
    }

    // A narrow range that straddles zero is just as well defined
    boost::random::uniform_int_distribution<int256> narrow_signed {int256{-1000}, int256{1000}};
    std::cout << "\nThree draws from [-1000, 1000]:" << std::endl;
    for (int i {0}; i < 3; ++i)
    {
        std::cout << "  " << narrow_signed(rng) << std::endl;
    }

    return 0;
}

Output:

=== Random uint256 values ===
Three draws over the whole range:
  15779298743775714632317536513379354586146892749525763423127136840744637424342
  43177445770928097090247088932550690159113821531843733955742654178868113840469
  60640964292598001157223999640664395760735199570965932942987833599385394609334

Three draws from [2^200, 2^200 + 1000]:
  1606938044258990275541962092341162602522202993782792835302061 (offset 685)
  1606938044258990275541962092341162602522202993782792835302013 (offset 637)
  1606938044258990275541962092341162602522202993782792835302203 (offset 827)

=== A random data set ===
Samples:               10000
Mean of the top word:  9.25951e+18
Expected (UINT64_MAX/2): 9.22337e+18
Within 2 percent:      true
Smallest draw has a nonzero top word: true
Largest draw is above 2^255:          true

=== Random int256 values ===
Three signed draws over the whole range:
  -48914518685495016003079655644702534743518077971924748443186506941468370631137
  21229253073386910577533090253496966441456620612659052547108350093323684705720
  -34152942800605455448740345423058401275269594265700221127640867476067807835176

Three draws from [-1000, 1000]:
  -46
  -976
  548

Boost.Charconv Integration

Example 15. This example demonstrates to_chars and from_chars in several bases, the error codes for a buffer that is too small, a value that is out of range, and text that is not a number, and the corresponding int256 round trip.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/charconv.hpp>
#include <boost/int256/limits.hpp>
#include <boost/charconv.hpp>
#include <cstring>
#include <iostream>
#include <limits>
#include <system_error>

int main()
{
    using boost::int256::uint256;

    // A 256-bit value needs 78 characters in base 10 and 256 in base 2, so size the
    // buffer for the base being used
    char buffer[260];

    // === to_chars: Convert integers to character strings ===
    std::cout << "=== to_chars ===" << std::endl;

    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};
    auto result {boost::charconv::to_chars(buffer, buffer + sizeof(buffer), max_value)};
    *result.ptr = '\0';
    std::cout << "uint256 max (decimal): " << buffer << std::endl;
    std::cout << "  characters written:  " << (result.ptr - buffer) << std::endl;

    // Hexadecimal output (base 16)
    constexpr uint256 hex_value {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678),
                                 UINT64_C(0x0F1E2D3C4B5A6978), UINT64_C(0x8796A5B4C3D2E1F0)};
    result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), hex_value, 16);
    *result.ptr = '\0';
    std::cout << "uint256 (hex): 0x" << buffer << std::endl;

    // Octal output (base 8)
    result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), uint256{511U}, 8);
    *result.ptr = '\0';
    std::cout << "uint256 511 (octal): 0" << buffer << std::endl;

    // Binary output (base 2), which is where the 256-character buffer matters
    result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), max_value, 2);
    *result.ptr = '\0';
    std::cout << "uint256 max in base 2 is " << (result.ptr - buffer) << " characters" << std::endl;

    // A buffer that is too small is reported rather than truncated
    char small[10];
    const auto too_small {boost::charconv::to_chars(small, small + sizeof(small), max_value)};
    std::cout << "to_chars into 10 chars: " << std::boolalpha << static_cast<bool>(too_small)
              << " (ec == value_too_large: "
              << (too_small.ec == std::errc::value_too_large) << ")" << std::endl;

    // === from_chars: Parse character strings to integers ===
    std::cout << "\n=== from_chars ===" << std::endl;

    const char* decimal_str {"115792089237316195423570985008687907853269984665640564039457584007913129639935"};
    uint256 parsed {};
    auto parse {boost::charconv::from_chars(decimal_str, decimal_str + std::strlen(decimal_str), parsed)};
    std::cout << "Parsed the 78-digit max:" << std::endl;
    std::cout << "  Result: " << parsed << std::endl;
    std::cout << "  Equals max? " << (parsed == max_value) << std::endl;
    std::cout << "  Consumed every character? " << (parse.ptr == decimal_str + std::strlen(decimal_str))
              << std::endl;

    // Parse hexadecimal (base 16)
    const char* hex_str {"DEADBEEFCAFEBABE123456780F1E2D3C4B5A69788796A5B4C3D2E1F0"};
    uint256 parsed_hex {};
    boost::charconv::from_chars(hex_str, hex_str + std::strlen(hex_str), parsed_hex, 16);
    std::cout << "\nParsed hex \"" << hex_str << "\"" << std::endl;
    std::cout << "  Result: " << parsed_hex << std::endl;
    std::cout << "  Round trips: " << (parsed_hex == hex_value) << std::endl;

    // One past the max is rejected rather than wrapped
    std::cout << "\n=== Error handling ===" << std::endl;
    const char* overflow_str {"115792089237316195423570985008687907853269984665640564039457584007913129639936"};
    uint256 unused {};
    const auto overflow {boost::charconv::from_chars(overflow_str,
                                                     overflow_str + std::strlen(overflow_str), unused)};
    std::cout << "max + 1 parses: " << static_cast<bool>(overflow)
              << " (ec == result_out_of_range: " << (overflow.ec == std::errc::result_out_of_range) << ")"
              << std::endl;

    const char* invalid_str {"not a number"};
    const auto invalid {boost::charconv::from_chars(invalid_str,
                                                    invalid_str + std::strlen(invalid_str), unused)};
    std::cout << "\"not a number\" parses: " << static_cast<bool>(invalid)
              << " (ec == invalid_argument: " << (invalid.ec == std::errc::invalid_argument) << ")"
              << std::endl;

    // Parsing stops at the first character that is not a digit of the base
    const char* prefixed {"0x10"};
    uint256 stopped {};
    const auto partial {boost::charconv::from_chars(prefixed, prefixed + std::strlen(prefixed), stopped, 16)};
    std::cout << "\"0x10\" in base 16: value " << stopped << ", stopped at '" << *partial.ptr << "'"
              << std::endl;

    std::cout << "\n=== to_chars and from_chars with int256 ===" << std::endl;

    using boost::int256::int256;

    constexpr int256 min_i256 {(std::numeric_limits<int256>::min)()};
    result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), min_i256);
    *result.ptr = '\0';
    std::cout << "int256 min (decimal): " << buffer << std::endl;

    // Sign-magnitude in every base: the '-' comes before any digits, and there
    // is no base prefix from to_chars itself (that is an iostream/format
    // feature), so the sign is simply the first character.
    result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), int256{-255}, 16);
    *result.ptr = '\0';
    std::cout << "int256 -255 (hex): " << buffer << std::endl;

    const char* negative_str {"-57896044618658097711785492504343953926634992332820282019728792003956564819968"};
    int256 parsed_signed {};
    boost::charconv::from_chars(negative_str, negative_str + std::strlen(negative_str), parsed_signed);
    std::cout << "\nParsed \"" << negative_str << "\"" << std::endl;
    std::cout << "  Result: " << parsed_signed << std::endl;
    std::cout << "  Equals min? " << (parsed_signed == min_i256) << std::endl;

    // A leading '+' is rejected, matching from_chars for the built-in types
    const char* plus_str {"+1"};
    int256 rejected {};
    const auto plus_result {boost::charconv::from_chars(plus_str, plus_str + std::strlen(plus_str), rejected)};
    std::cout << "\"+1\" parses: " << static_cast<bool>(plus_result)
              << " (ec == invalid_argument: " << (plus_result.ec == std::errc::invalid_argument) << ")"
              << std::endl;

    return 0;
}

Output:

=== to_chars ===
uint256 max (decimal): 115792089237316195423570985008687907853269984665640564039457584007913129639935
  characters written:  78
uint256 (hex): 0xdeadbeefcafebabe123456780f1e2d3c4b5a69788796a5b4c3d2e1f0
uint256 511 (octal): 0777
uint256 max in base 2 is 256 characters
to_chars into 10 chars: false (ec == value_too_large: true)

=== from_chars ===
Parsed the 78-digit max:
  Result: 115792089237316195423570985008687907853269984665640564039457584007913129639935
  Equals max? true
  Consumed every character? true

Parsed hex "DEADBEEFCAFEBABE123456780F1E2D3C4B5A69788796A5B4C3D2E1F0"
  Result: 23450803645956985397271062114592904319886246494160595176910461526512
  Round trips: true

=== Error handling ===
max + 1 parses: false (ec == result_out_of_range: true)
"not a number" parses: false (ec == invalid_argument: true)
"0x10" in base 16: value 0, stopped at 'x'

=== to_chars and from_chars with int256 ===
int256 min (decimal): -57896044618658097711785492504343953926634992332820282019728792003956564819968
int256 -255 (hex): -ff

Parsed "-57896044618658097711785492504343953926634992332820282019728792003956564819968"
  Result: -57896044618658097711785492504343953926634992332820282019728792003956564819968
  Equals min? true
"+1" parses: false (ec == invalid_argument: true)

Boost.ContainerHash and Boost.Unordered Integration

Example 16. This example demonstrates using uint256 or int256 as a key in the Boost.Unordered containers, and combining two of them into a composite key with boost::hash_combine.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// This example demonstrates Boost.ContainerHash integration with uint256.
// Including <boost/int256/hash.hpp> injects a hash_value overload for the library
// type, so boost::hash, boost::hash_combine, boost::hash_range, and the
// boost::unordered containers (which default to boost::hash) all work with no
// extra configuration.

#include <boost/int256/int256.hpp>
#include <boost/int256/hash.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>
#include <boost/container_hash/hash.hpp>
#include <boost/unordered/unordered_map.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <cstddef>
#include <functional>
#include <iostream>
#include <limits>
#include <string>

using boost::int256::uint256;
using boost::int256::int256;

// A user-defined composite key that holds 256-bit fields. Providing a hash_value
// overload in the type's own namespace lets Boost.ContainerHash find it via ADL,
// and boost::hash_combine reuses the uint256 hash supplied by hash.hpp.
struct point
{
    uint256 x;
    uint256 y;
};

bool operator==(const point& lhs, const point& rhs)
{
    return lhs.x == rhs.x && lhs.y == rhs.y;
}

std::size_t hash_value(const point& p)
{
    std::size_t seed {0};
    boost::hash_combine(seed, p.x);
    boost::hash_combine(seed, p.y);
    return seed;
}

int main()
{
    std::cout << "=== boost::hash on uint256 ===" << std::endl;

    // boost::hash<T> dispatches to the hash_value overload from hash.hpp, which
    // delegates to std::hash, so the two functors always agree.
    const uint256 big {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678),
                       UINT64_C(0x0F1E2D3C4B5A6978), UINT64_C(0x8796A5B4C3D2E1F0)};

    std::cout << std::boolalpha;
    std::cout << "boost::hash matches std::hash: "
              << (boost::hash<uint256>{}(big) == std::hash<uint256>{}(big)) << std::endl;

    // Every word takes part in the hash, so a permutation of the words is a
    // different key
    const uint256 permuted {UINT64_C(0x8796A5B4C3D2E1F0), UINT64_C(0x0F1E2D3C4B5A6978),
                            UINT64_C(0xCAFEBABE12345678), UINT64_C(0xDEADBEEF)};
    std::cout << "Reversed words hash differently: "
              << (boost::hash<uint256>{}(big) != boost::hash<uint256>{}(permuted)) << std::endl;

    std::cout << "\n=== boost::unordered_map<uint256, ...> ===" << std::endl;

    // boost::unordered_map defaults to boost::hash<Key>, so uint256 keys need
    // no explicit hasher.
    boost::unordered_map<uint256, std::string> labels {};
    labels[uint256{1U} << 64U] = "two to the sixty-fourth";
    labels[uint256{1U} << 128U] = "two to the one hundred twenty-eighth";
    labels[uint256{1U} << 255U] = "two to the two hundred fifty-fifth";
    labels[uint256{42U}] = "forty-two";

    std::cout << "Entries: " << labels.size() << std::endl;
    std::cout << "Label at 2^128: " << labels[uint256{1U} << 128U] << std::endl;
    std::cout << "Contains 42: " << (labels.find(uint256{42U}) != labels.end()) << std::endl;

    std::cout << "\n=== hash_combine for a composite key ===" << std::endl;

    // The point hasher combines two uint256 fields; boost::hash<point> finds it
    // via ADL, letting point be used as a key directly.
    boost::unordered_map<point, long> populations {};
    populations[point{uint256{10U}, uint256{20U}}] = 5000000;
    populations[point{uint256{1U} << 200U, uint256{40U}}] = 250000;

    std::cout << "Points stored: " << populations.size() << std::endl;
    std::cout << "Value at (10, 20): " << populations[point{uint256{10U}, uint256{20U}}] << std::endl;
    std::cout << "Same coordinate hashes equal: "
              << (boost::hash<point>{}(point{uint256{10U}, uint256{20U}}) ==
                  boost::hash<point>{}(point{uint256{10U}, uint256{20U}})) << std::endl;

    std::cout << "\n=== boost::unordered_flat_map<uint256, ...> ===" << std::endl;

    // The modern flat container also defaults to boost::hash.
    boost::unordered_flat_map<uint256, int> counts {};
    counts[uint256{0U}] = 1;
    counts[uint256{1U}] = 2;
    counts[~uint256{}] = 3;

    std::cout << "Flat map size: " << counts.size() << std::endl;
    std::cout << "counts[max] = " << counts[~uint256{}] << std::endl;

    std::cout << "\n=== Using a 256-bit hash as a key ===" << std::endl;

    // The obvious use for a 256-bit integer key: a digest, stored and looked up as
    // one integer rather than as a byte array
    boost::unordered_map<uint256, std::string> by_digest {};
    const uint256 digest {UINT64_C(0xE3B0C44298FC1C14), UINT64_C(0x9AFBF4C8996FB924),
                          UINT64_C(0x27AE41E4649B934C), UINT64_C(0xA495991B7852B855)};
    by_digest[digest] = "the empty string";

    std::cout << "Lookup by digest: " << by_digest[digest] << std::endl;
    std::cout << "Digest as an integer: " << digest << std::endl;

    std::cout << "\n=== boost::hash on int256 ===" << std::endl;

    // int256's hash_value follows the same algorithm as uint256's, over the
    // same four words, so boost::hash<int256> agrees with std::hash<int256>
    // the same way.
    const int256 neg {-123456789012345678LL};

    std::cout << "boost::hash matches std::hash (int256): "
              << (boost::hash<int256>{}(neg) == std::hash<int256>{}(neg)) << std::endl;

    // A value and its negation differ in every word once the sign bit is
    // sign-extended, so they hash differently
    std::cout << "hash(v) != hash(-v): "
              << (boost::hash<int256>{}(neg) != boost::hash<int256>{}(-neg)) << std::endl;

    std::cout << "\n=== boost::unordered_flat_map<int256, ...> ===" << std::endl;

    boost::unordered_flat_map<int256, int> signed_counts {};
    signed_counts[int256{-1}] = 1;
    signed_counts[int256{0}] = 2;
    signed_counts[int256{1}] = 3;
    signed_counts[(std::numeric_limits<int256>::min)()] = 4;

    std::cout << "Flat map size: " << signed_counts.size() << std::endl;
    std::cout << "counts[-1] = " << signed_counts[int256{-1}] << std::endl;
    std::cout << "counts[min] = " << signed_counts[(std::numeric_limits<int256>::min)()] << std::endl;

    return 0;
}

Output:

=== boost::hash on uint256 ===
boost::hash matches std::hash: true
Reversed words hash differently: true

=== boost::unordered_map<uint256, ...> ===
Entries: 4
Label at 2^128: two to the one hundred twenty-eighth
Contains 42: true

=== hash_combine for a composite key ===
Points stored: 2
Value at (10, 20): 5000000
Same coordinate hashes equal: true

=== boost::unordered_flat_map<uint256, ...> ===
Flat map size: 3
counts[max] = 3

=== Using a 256-bit hash as a key ===
Lookup by digest: the empty string
Digest as an integer: 102987336249554097029535212322581322789799900648198034993379397001115665086549

=== boost::hash on int256 ===
boost::hash matches std::hash (int256): true
hash(v) != hash(-v): true

=== boost::unordered_flat_map<int256, ...> ===
Flat map size: 4
counts[-1] = 1
counts[min] = 4

String Conversion (to_string)

Example 17. This example demonstrates to_string and to_wstring for both uint256 and int256, and that the result agrees with std::to_string wherever the value fits in 64 bits.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/string.hpp>
#include <boost/int256/literals.hpp>
#include <boost/int256/limits.hpp>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>

int main()
{
    using boost::int256::uint256;
    using boost::int256::to_string;
    using namespace boost::int256::literals;

    std::cout << "=== to_string with uint256 ===" << std::endl;

    // For values that fit in 64 bits the result matches std::to_string exactly
    constexpr uint256 small {UINT64_C(1234567890)};
    const auto small_str {to_string(small)};
    const auto small_std {std::to_string(std::uint64_t{1234567890})};
    std::cout << "to_string(1234567890):               " << small_str << std::endl;
    std::cout << "std::to_string(uint64_t 1234567890): " << small_std << std::endl;
    std::cout << "Match: " << std::boolalpha << (small_str == small_std) << std::endl;

    constexpr uint256 max64 {UINT64_MAX};
    const auto max64_str {to_string(max64)};
    const auto max64_std {std::to_string(UINT64_MAX)};
    std::cout << "\nto_string(UINT64_MAX):      " << max64_str << std::endl;
    std::cout << "std::to_string(UINT64_MAX): " << max64_std << std::endl;
    std::cout << "Match: " << (max64_str == max64_std) << std::endl;

    std::cout << "\n=== Values beyond the 64-bit range ===" << std::endl;

    // Every word boundary, to show that nothing is truncated
    std::cout << "2^64:  " << to_string(uint256{1U} << 64U) << std::endl;
    std::cout << "2^128: " << to_string(uint256{1U} << 128U) << std::endl;
    std::cout << "2^192: " << to_string(uint256{1U} << 192U) << std::endl;
    std::cout << "2^255: " << to_string(uint256{1U} << 255U) << std::endl;

    const auto max_value {(std::numeric_limits<uint256>::max)()};
    const auto max_str {to_string(max_value)};
    std::cout << "uint256 max: " << max_str << std::endl;
    std::cout << "Digits in the max: " << max_str.size() << std::endl;

    std::cout << "\n=== Round trip through a literal ===" << std::endl;

    const auto from_literal {115792089237316195423570985008687907853269984665640564039457584007913129639935_u256};
    std::cout << "Literal equals the max: " << (from_literal == max_value) << std::endl;
    std::cout << "to_string of it round trips: " << (to_string(from_literal) == max_str) << std::endl;

    std::cout << "\n=== to_wstring ===" << std::endl;

    // to_wstring produces the same digits as a std::wstring
    const auto wide {boost::int256::to_wstring(small)};
    std::cout << "to_wstring(1234567890) has " << wide.size() << " characters" << std::endl;
    std::cout << "Same digits as to_string: "
              << (std::wstring{small_str.begin(), small_str.end()} == wide) << std::endl;

    std::cout << "\n=== to_string with int256 ===" << std::endl;

    using boost::int256::int256;

    // For values that fit in 64 bits the result matches std::to_string exactly,
    // including the sign
    constexpr int256 s_negative {-42};
    const auto s_neg_str {to_string(s_negative)};
    const auto s_neg_std {std::to_string(std::int64_t{-42})};
    std::cout << "to_string(-42):              " << s_neg_str << std::endl;
    std::cout << "std::to_string(int64_t -42): " << s_neg_std << std::endl;
    std::cout << "Match: " << (s_neg_str == s_neg_std) << std::endl;

    constexpr int256 s_large {INT64_MAX};
    const auto s_large_str {to_string(s_large)};
    const auto s_large_std {std::to_string(INT64_MAX)};
    std::cout << "\nto_string(INT64_MAX):       " << s_large_str << std::endl;
    std::cout << "std::to_string(INT64_MAX): " << s_large_std << std::endl;
    std::cout << "Match: " << (s_large_str == s_large_std) << std::endl;

    std::cout << "\n=== int256 values beyond the 64-bit range ===" << std::endl;

    // INT256_MIN via a negated numeric literal fails: the sign is not part of
    // the literal, so the positive magnitude 2^255 is read first and rejected
    // as out of range. A string literal keeps the sign with the digits, so it
    // parses cleanly, and so does the macro.
    const auto large_negative {"-57896044618658097711785492504343953926634992332820282019728792003956564819968"_i256};
    std::cout << "int256 min with string literal: " << to_string(large_negative) << std::endl;

    const auto large_negative_c {BOOST_INT256_INT256_C(-57896044618658097711785492504343953926634992332820282019728792003956564819968)};
    std::cout << "int256 min with INT256_C macro: " << to_string(large_negative_c) << std::endl;

    const auto large_positive {(std::numeric_limits<int256>::max)()};
    std::cout << "int256 max: " << to_string(large_positive) << std::endl;

    return 0;
}

Output:

=== to_string with uint256 ===
to_string(1234567890):               1234567890
std::to_string(uint64_t 1234567890): 1234567890
Match: true

to_string(UINT64_MAX):      18446744073709551615
std::to_string(UINT64_MAX): 18446744073709551615
Match: true

=== Values beyond the 64-bit range ===
2^64:  18446744073709551616
2^128: 340282366920938463463374607431768211456
2^192: 6277101735386680763835789423207666416102355444464034512896
2^255: 57896044618658097711785492504343953926634992332820282019728792003956564819968
uint256 max: 115792089237316195423570985008687907853269984665640564039457584007913129639935
Digits in the max: 78

=== Round trip through a literal ===
Literal equals the max: true
to_string of it round trips: true

=== to_wstring ===
to_wstring(1234567890) has 10 characters
Same digits as to_string: true

=== to_string with int256 ===
to_string(-42):              -42
std::to_string(int64_t -42): -42
Match: true

to_string(INT64_MAX):       9223372036854775807
std::to_string(INT64_MAX): 9223372036854775807
Match: true

=== int256 values beyond the 64-bit range ===
int256 min with string literal: -57896044618658097711785492504343953926634992332820282019728792003956564819968
int256 min with INT256_C macro: -57896044618658097711785492504343953926634992332820282019728792003956564819968
int256 max: 57896044618658097711785492504343953926634992332820282019728792003956564819967

{fmt} Integration

Example 18. This example demonstrates the {fmt} formatter: presentation types, the alternate form, sign options, and the fill, align and width specifiers, ending with `int256’s sign-magnitude formatting.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// This example demonstrates {fmt} library integration with uint256.
// Requires {fmt} to be installed: https://github.com/fmtlib/fmt
//
// For C++20 <format> support use <boost/int256/format.hpp> instead. See
// format.cpp, which is this same program with fmt:: replaced by std::.

#include <boost/int256/int256.hpp>
#include <boost/int256/fmt_format.hpp>
#include <boost/int256/limits.hpp>

#include <fmt/format.h>
#include <iostream>
#include <limits>

int main()
{
    using boost::int256::uint256;

    std::cout << "=== Basic Formatting ===" << std::endl;

    constexpr uint256 wide_value {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678),
                                  UINT64_C(0x0F1E2D3C4B5A6978), UINT64_C(0x8796A5B4C3D2E1F0)};

    // Default decimal formatting
    std::cout << fmt::format("Default (decimal): {}", wide_value) << std::endl;
    std::cout << fmt::format("Small value: {}", uint256 {42U}) << std::endl;

    std::cout << "\n=== Base Specifiers ===" << std::endl;

    // Different bases: binary, octal, decimal, hex
    constexpr uint256 value {255U};
    std::cout << fmt::format("Binary:      {:b}", value) << std::endl;
    std::cout << fmt::format("Octal:       {:o}", value) << std::endl;
    std::cout << fmt::format("Decimal:     {:d}", value) << std::endl;
    std::cout << fmt::format("Hexadecimal: {:x}", value) << std::endl;
    std::cout << fmt::format("Hex (upper): {:X}", value) << std::endl;

    std::cout << "\n=== Alternate Form (Prefixes) ===" << std::endl;

    // Using # for alternate form adds base prefixes
    std::cout << fmt::format("Binary with prefix:  {:#b}", value) << std::endl;
    std::cout << fmt::format("Octal with prefix:   {:#o}", value) << std::endl;
    std::cout << fmt::format("Hex with prefix:     {:#x}", value) << std::endl;
    std::cout << fmt::format("Hex upper prefix:    {:#X}", value) << std::endl;

    std::cout << "\n=== Sign Options ===" << std::endl;

    // Sign specifiers: + (always show), - (default), space (space for positive)
    std::cout << fmt::format("Plus sign:  {:+}", value) << std::endl;
    std::cout << fmt::format("Minus only: {}", value) << std::endl;
    std::cout << fmt::format("Space sign: {: }", value) << std::endl;

    std::cout << "\n=== Zero Padding ===" << std::endl;

    // Padding with zeros (no alignment specifier)
    std::cout << fmt::format("8-digit padding:  {:08}", value) << std::endl;
    std::cout << fmt::format("16-digit padding: {:016}", value) << std::endl;

    std::cout << "\n=== Alignment ===" << std::endl;

    // Left, right, and center alignment with default fill (space)
    std::cout << fmt::format("Left align:   '{:<10}'", value) << std::endl;
    std::cout << fmt::format("Right align:  '{:>10}'", value) << std::endl;
    std::cout << fmt::format("Center align: '{:^10}'", value) << std::endl;

    std::cout << "\n=== Alignment with Fill Characters ===" << std::endl;

    // Custom fill characters
    std::cout << fmt::format("Left with *:   '{:*<10}'", value) << std::endl;
    std::cout << fmt::format("Right with 0:  '{:0>10}'", value) << std::endl;
    std::cout << fmt::format("Center with -: '{:-^10}'", value) << std::endl;

    std::cout << "\n=== Alignment with Hex and Prefix ===" << std::endl;

    // Alignment with base specifiers and prefixes
    std::cout << fmt::format("Right align hex:  '{:>10x}'", value) << std::endl;
    std::cout << fmt::format("Left align hex:   '{:<10x}'", value) << std::endl;
    std::cout << fmt::format("Center with prefix: '{:*^12x}'", value) << std::endl;

    std::cout << "\n=== Large Values ===" << std::endl;

    // Demonstrate with values beyond the 64-bit and 128-bit ranges
    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};

    std::cout << fmt::format("uint256 max:       {}", max_value) << std::endl;
    std::cout << fmt::format("uint256 max (hex): {:#x}", max_value) << std::endl;
    std::cout << fmt::format("2^200:             {}", uint256 {1U} << 200U) << std::endl;

    // A width wider than the number pads it, which is how the 78-digit decimal and the
    // 64-digit hex forms line up in a table
    std::cout << fmt::format("Padded to 80:  '{:>80}'", uint256 {1U} << 200U) << std::endl;
    std::cout << fmt::format("Zero-filled:   '{:064x}'", uint256 {1U} << 200U) << std::endl;

    std::cout << "\n=== Combined Format Specifiers ===" << std::endl;

    // Combining multiple specifiers
    std::cout << fmt::format("Hex with prefix, uppercase, padded: {:#070X}", wide_value) << std::endl;
    std::cout << fmt::format("Decimal with plus, padded: {:+080}", wide_value) << std::endl;

    std::cout << "\n=== int256 Formatting ===" << std::endl;

    using boost::int256::int256;

    constexpr int256 signed_value {-123456789012345678LL};
    std::cout << fmt::format("Signed value: {}", signed_value) << std::endl;

    constexpr int256 positive {42};
    constexpr int256 negative {-42};

    std::cout << fmt::format("Plus sign:  {:+} and {:+}", positive, negative) << std::endl;
    std::cout << fmt::format("Minus only: {} and {}", positive, negative) << std::endl;
    std::cout << fmt::format("Space sign: {: } and {: }", positive, negative) << std::endl;

    std::cout << "\n=== Sign-Magnitude in Every Base ===" << std::endl;

    // The sign always comes before the base prefix and any zero padding, so
    // '-0xff' and '-00000042', never '0x-ff'
    std::cout << fmt::format("Negative hex with prefix: {:#x}", int256{-255}) << std::endl;
    std::cout << fmt::format("Negative, zero-padded:    {:09}", int256{-42}) << std::endl;

    std::cout << "\n=== Alignment with Sign ===" << std::endl;

    std::cout << fmt::format("Right align +:  '{:>+10}'", positive) << std::endl;
    std::cout << fmt::format("Left align +:   '{:<+10}'", positive) << std::endl;
    std::cout << fmt::format("Center align +: '{:^+11}'", positive) << std::endl;
    std::cout << fmt::format("Right align -:  '{:*>10}'", negative) << std::endl;

    std::cout << "\n=== Large Signed Values ===" << std::endl;

    constexpr auto min_value {(std::numeric_limits<int256>::min)()};
    std::cout << fmt::format("int256 min:       {}", min_value) << std::endl;
    std::cout << fmt::format("int256 min (hex): {:#x}", min_value) << std::endl;

    return 0;
}

Output:

=== Basic Formatting ===
Default (decimal): 23450803645956985397271062114592904319886246494160595176910461526512
Small value: 42

=== Base Specifiers ===
Binary:      11111111
Octal:       377
Decimal:     255
Hexadecimal: ff
Hex (upper): FF

=== Alternate Form (Prefixes) ===
Binary with prefix:  0b11111111
Octal with prefix:   0377
Hex with prefix:     0xff
Hex upper prefix:    0XFF

=== Sign Options ===
Plus sign:  +255
Minus only: 255
Space sign:  255

=== Zero Padding ===
8-digit padding:  00000255
16-digit padding: 0000000000000255

=== Alignment ===
Left align:   '255       '
Right align:  '       255'
Center align: '   255    '

=== Alignment with Fill Characters ===
Left with *:   '255*******'
Right with 0:  '0000000255'
Center with -: '---255----'

=== Alignment with Hex and Prefix ===
Right align hex:  '        ff'
Left align hex:   'ff        '
Center with prefix: '*****ff*****'

=== Large Values ===
uint256 max:       115792089237316195423570985008687907853269984665640564039457584007913129639935
uint256 max (hex): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2^200:             1606938044258990275541962092341162602522202993782792835301376
Padded to 80:  '                   1606938044258990275541962092341162602522202993782792835301376'
Zero-filled:   '0000000000000100000000000000000000000000000000000000000000000000'

=== Combined Format Specifiers ===
Hex with prefix, uppercase, padded: 0X000000000000DEADBEEFCAFEBABE123456780F1E2D3C4B5A69788796A5B4C3D2E1F0
Decimal with plus, padded: +0000000000023450803645956985397271062114592904319886246494160595176910461526512

=== int256 Formatting ===
Signed value: -123456789012345678
Plus sign:  +42 and -42
Minus only: 42 and -42
Space sign:  42 and -42

=== Sign-Magnitude in Every Base ===
Negative hex with prefix: -0xff
Negative, zero-padded:    -00000042

=== Alignment with Sign ===
Right align +:  '       +42'
Left align +:   '+42       '
Center align +: '    +42    '
Right align -:  '*******-42'

=== Large Signed Values ===
int256 min:       -57896044618658097711785492504343953926634992332820282019728792003956564819968
int256 min (hex): -0x8000000000000000000000000000000000000000000000000000000000000000

<format> Integration

Example 19. This example demonstrates the same format specifiers through C++20 std::format, plus formatted_size and format_to, ending with `int256’s sign-magnitude formatting.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// This example demonstrates std::format support for uint256.
// Including <boost/int256/format.hpp> defines the std::formatter specialization,
// and BOOST_INT256_HAS_FORMAT reports whether C++20 <format> was available.
//
// Apart from the final section, this is the same program as fmt_format.cpp with
// fmt:: replaced by std::, and those lines of output are identical.

#include <boost/int256/int256.hpp>
#include <boost/int256/format.hpp>
#include <boost/int256/limits.hpp>

#include <format>
#include <iostream>
#include <iterator>
#include <limits>
#include <string>

#ifdef BOOST_INT256_HAS_CONSTEXPR_FORMAT

// With C++26 constexpr std::format the entire call happens at compile time
static_assert(std::format("{:#x}", boost::int256::uint256 {255U}) == "0xff");
static_assert(std::format("{}", boost::int256::uint256 {42U}) == "42");
static_assert(std::format("{:+}", boost::int256::int256 {-42}) == "-42");

#endif

int main()
{
    using boost::int256::uint256;

    std::cout << "=== Basic Formatting ===" << std::endl;

    constexpr uint256 wide_value {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678),
                                  UINT64_C(0x0F1E2D3C4B5A6978), UINT64_C(0x8796A5B4C3D2E1F0)};

    // Default decimal formatting
    std::cout << std::format("Default (decimal): {}", wide_value) << std::endl;
    std::cout << std::format("Small value: {}", uint256 {42U}) << std::endl;

    std::cout << "\n=== Base Specifiers ===" << std::endl;

    // Different bases: binary, octal, decimal, hex
    constexpr uint256 value {255U};
    std::cout << std::format("Binary:      {:b}", value) << std::endl;
    std::cout << std::format("Octal:       {:o}", value) << std::endl;
    std::cout << std::format("Decimal:     {:d}", value) << std::endl;
    std::cout << std::format("Hexadecimal: {:x}", value) << std::endl;
    std::cout << std::format("Hex (upper): {:X}", value) << std::endl;

    std::cout << "\n=== Alternate Form (Prefixes) ===" << std::endl;

    // Using # for alternate form adds base prefixes
    std::cout << std::format("Binary with prefix:  {:#b}", value) << std::endl;
    std::cout << std::format("Octal with prefix:   {:#o}", value) << std::endl;
    std::cout << std::format("Hex with prefix:     {:#x}", value) << std::endl;
    std::cout << std::format("Hex upper prefix:    {:#X}", value) << std::endl;

    std::cout << "\n=== Sign Options ===" << std::endl;

    // Sign specifiers: + (always show), - (default), space (space for positive)
    std::cout << std::format("Plus sign:  {:+}", value) << std::endl;
    std::cout << std::format("Minus only: {}", value) << std::endl;
    std::cout << std::format("Space sign: {: }", value) << std::endl;

    std::cout << "\n=== Zero Padding ===" << std::endl;

    // Padding with zeros (no alignment specifier)
    std::cout << std::format("8-digit padding:  {:08}", value) << std::endl;
    std::cout << std::format("16-digit padding: {:016}", value) << std::endl;

    std::cout << "\n=== Alignment ===" << std::endl;

    // Left, right, and center alignment with default fill (space)
    std::cout << std::format("Left align:   '{:<10}'", value) << std::endl;
    std::cout << std::format("Right align:  '{:>10}'", value) << std::endl;
    std::cout << std::format("Center align: '{:^10}'", value) << std::endl;

    std::cout << "\n=== Alignment with Fill Characters ===" << std::endl;

    // Custom fill characters
    std::cout << std::format("Left with *:   '{:*<10}'", value) << std::endl;
    std::cout << std::format("Right with 0:  '{:0>10}'", value) << std::endl;
    std::cout << std::format("Center with -: '{:-^10}'", value) << std::endl;

    std::cout << "\n=== Alignment with Hex and Prefix ===" << std::endl;

    // Alignment with base specifiers and prefixes
    std::cout << std::format("Right align hex:  '{:>10x}'", value) << std::endl;
    std::cout << std::format("Left align hex:   '{:<10x}'", value) << std::endl;
    std::cout << std::format("Center with prefix: '{:*^12x}'", value) << std::endl;

    std::cout << "\n=== Large Values ===" << std::endl;

    // Demonstrate with values beyond the 64-bit and 128-bit ranges
    constexpr auto max_value {(std::numeric_limits<uint256>::max)()};

    std::cout << std::format("uint256 max:       {}", max_value) << std::endl;
    std::cout << std::format("uint256 max (hex): {:#x}", max_value) << std::endl;
    std::cout << std::format("2^200:             {}", uint256 {1U} << 200U) << std::endl;

    // A width wider than the number pads it, which is how the 78-digit decimal and the
    // 64-digit hex forms line up in a table
    std::cout << std::format("Padded to 80:  '{:>80}'", uint256 {1U} << 200U) << std::endl;
    std::cout << std::format("Zero-filled:   '{:064x}'", uint256 {1U} << 200U) << std::endl;

    std::cout << "\n=== Combined Format Specifiers ===" << std::endl;

    // Combining multiple specifiers
    std::cout << std::format("Hex with prefix, uppercase, padded: {:#070X}", wide_value) << std::endl;
    std::cout << std::format("Decimal with plus, padded: {:+080}", wide_value) << std::endl;

    std::cout << "\n=== Formatting Into an Existing Buffer ===" << std::endl;

    // formatted_size gives the exact length, and format_to writes through any
    // output iterator, so no intermediate std::string is required
    const auto length {std::formatted_size("{:#x}", max_value)};
    std::string buffer {};
    buffer.reserve(length);
    std::format_to(std::back_inserter(buffer), "{:#x}", max_value);

    std::cout << std::format("formatted_size: {}", length) << std::endl;
    std::cout << std::format("format_to:      {}", buffer) << std::endl;
    std::cout << std::format("Lengths match:  {}", buffer.size() == length) << std::endl;

    std::cout << "\n=== int256 Formatting ===" << std::endl;

    using boost::int256::int256;

    constexpr int256 signed_value {-123456789012345678LL};
    std::cout << std::format("Signed value: {}", signed_value) << std::endl;

    constexpr int256 positive {42};
    constexpr int256 negative {-42};

    // Sign specifiers work the same as for uint256, with the sign int256
    // already carries taking the place of the always-absent uint256 sign
    std::cout << std::format("Plus sign:  {:+} and {:+}", positive, negative) << std::endl;
    std::cout << std::format("Minus only: {} and {}", positive, negative) << std::endl;
    std::cout << std::format("Space sign: {: } and {: }", positive, negative) << std::endl;

    std::cout << "\n=== Sign-Magnitude in Every Base ===" << std::endl;

    // The sign always comes before the base prefix and any zero padding, so
    // '-0xff' and '-00000042', never '0x-ff'
    std::cout << std::format("Negative hex with prefix: {:#x}", int256{-255}) << std::endl;
    std::cout << std::format("Negative, zero-padded:    {:09}", int256{-42}) << std::endl;

    std::cout << "\n=== Alignment with Sign ===" << std::endl;

    std::cout << std::format("Right align +:  '{:>+10}'", positive) << std::endl;
    std::cout << std::format("Left align +:   '{:<+10}'", positive) << std::endl;
    std::cout << std::format("Center align +: '{:^+11}'", positive) << std::endl;
    std::cout << std::format("Right align -:  '{:*>10}'", negative) << std::endl;

    std::cout << "\n=== Large Signed Values ===" << std::endl;

    constexpr auto min_value {(std::numeric_limits<int256>::min)()};
    std::cout << std::format("int256 min:       {}", min_value) << std::endl;
    std::cout << std::format("int256 min (hex): {:#x}", min_value) << std::endl;

    return 0;
}

Output:

=== Basic Formatting ===
Default (decimal): 23450803645956985397271062114592904319886246494160595176910461526512
Small value: 42

=== Base Specifiers ===
Binary:      11111111
Octal:       377
Decimal:     255
Hexadecimal: ff
Hex (upper): FF

=== Alternate Form (Prefixes) ===
Binary with prefix:  0b11111111
Octal with prefix:   0377
Hex with prefix:     0xff
Hex upper prefix:    0XFF

=== Sign Options ===
Plus sign:  +255
Minus only: 255
Space sign:  255

=== Zero Padding ===
8-digit padding:  00000255
16-digit padding: 0000000000000255

=== Alignment ===
Left align:   '255       '
Right align:  '       255'
Center align: '   255    '

=== Alignment with Fill Characters ===
Left with *:   '255*******'
Right with 0:  '0000000255'
Center with -: '---255----'

=== Alignment with Hex and Prefix ===
Right align hex:  '        ff'
Left align hex:   'ff        '
Center with prefix: '*****ff*****'

=== Large Values ===
uint256 max:       115792089237316195423570985008687907853269984665640564039457584007913129639935
uint256 max (hex): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2^200:             1606938044258990275541962092341162602522202993782792835301376
Padded to 80:  '                   1606938044258990275541962092341162602522202993782792835301376'
Zero-filled:   '0000000000000100000000000000000000000000000000000000000000000000'

=== Combined Format Specifiers ===
Hex with prefix, uppercase, padded: 0X000000000000DEADBEEFCAFEBABE123456780F1E2D3C4B5A69788796A5B4C3D2E1F0
Decimal with plus, padded: +0000000000023450803645956985397271062114592904319886246494160595176910461526512

=== Formatting Into an Existing Buffer ===
formatted_size: 66
format_to:      0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Lengths match:  true

=== int256 Formatting ===
Signed value: -123456789012345678
Plus sign:  +42 and -42
Minus only: 42 and -42
Space sign:  42 and -42

=== Sign-Magnitude in Every Base ===
Negative hex with prefix: -0xff
Negative, zero-padded:    -00000042

=== Alignment with Sign ===
Right align +:  '       +42'
Left align +:   '+42       '
Center align +: '    +42    '
Right align -:  '*******-42'

=== Large Signed Values ===
int256 min:       -57896044618658097711785492504343953926634992332820282019728792003956564819968
int256 min (hex): -0x8000000000000000000000000000000000000000000000000000000000000000

<cstdlib> support (Combined div and mod)

Example 20. This example demonstrates div, which returns the quotient and the remainder of a single division, for uint256 and for int256 (including every sign combination and MIN / -1).
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/int256/int256.hpp>
#include <boost/int256/cstdlib.hpp>
#include <boost/int256/iostream.hpp>
#include <boost/int256/limits.hpp>
#include <iostream>
#include <limits>

int main()
{
    using boost::int256::uint256;
    using boost::int256::int256;
    using boost::int256::div;

    std::cout << "=== div() Function ===" << std::endl;
    std::cout << "Returns both quotient and remainder from a single division" << std::endl;

    // Unlike a hardware type, a compiler cannot fold a 256-bit division followed by a
    // modulo of the same operands into one operation. The division algorithm produces
    // both halves anyway, so div() hands them back together and costs no more than one.

    std::cout << "\n--- One word divisor ---" << std::endl;

    constexpr uint256 dividend {1000000000000000000ULL};
    constexpr uint256 divisor {7U};

    const auto result {div(dividend, divisor)};
    std::cout << dividend << " / " << divisor << " = " << result.quot
              << " remainder " << result.rem << std::endl;

    // Verify: quot * divisor + rem == dividend
    std::cout << "Verification: " << result.quot << " * " << divisor
              << " + " << result.rem << " = " << (result.quot * divisor + result.rem) << std::endl;

    std::cout << "\n--- Powers of two ---" << std::endl;

    constexpr uint256 large_dividend {uint256{1U} << 200U};
    constexpr uint256 large_divisor {uint256{1U} << 100U};

    const auto large_result {div(large_dividend, large_divisor)};
    std::cout << "2^200 / 2^100 = " << large_result.quot
              << " remainder " << large_result.rem << std::endl;

    std::cout << "\n--- Multi word divisor ---" << std::endl;

    // A divisor with three significant words takes the general path
    constexpr uint256 wide_divisor {UINT64_C(0), UINT64_C(0x1), UINT64_C(0x2), UINT64_C(0x3)};
    constexpr uint256 wide_dividend {UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX};

    const auto wide_result {div(wide_dividend, wide_divisor)};
    std::cout << "max / " << wide_divisor << std::endl;
    std::cout << "  quotient  = " << wide_result.quot << std::endl;
    std::cout << "  remainder = " << wide_result.rem << std::endl;
    std::cout << "  quot * divisor + rem == max: " << std::boolalpha
              << (wide_result.quot * wide_divisor + wide_result.rem == wide_dividend) << std::endl;

    std::cout << "\n--- Edge cases ---" << std::endl;

    const auto small_div {div(uint256{3U}, uint256{10U})};
    std::cout << "3 / 10 = " << small_div.quot << " remainder " << small_div.rem << std::endl;

    const auto exact {div(uint256{100U}, uint256{25U})};
    std::cout << "100 / 25 = " << exact.quot << " remainder " << exact.rem << std::endl;

    const auto by_self {div(wide_divisor, wide_divisor)};
    std::cout << "x / x = " << by_self.quot << " remainder " << by_self.rem << std::endl;

    std::cout << "\n=== div() with int256 (returns i256div_t) ===" << std::endl;

    // Signed division. The division works on the uint256 magnitudes of the
    // operands, so quot's sign is sign(x) != sign(y) and rem takes the sign
    // of the dividend, exactly matching the built-in signed division.
    constexpr int256 signed_dividend {-100};
    constexpr int256 signed_divisor {7};

    const auto sresult {div(signed_dividend, signed_divisor)};
    std::cout << signed_dividend << " / " << signed_divisor << " = " << sresult.quot
              << " remainder " << sresult.rem << std::endl;

    std::cout << "\n--- Sign Combinations ---" << std::endl;

    constexpr int256 pos {17};
    constexpr int256 neg {-17};
    constexpr int256 div_pos {5};
    constexpr int256 div_neg {-5};

    const auto pp {div(pos, div_pos)};
    const auto pn {div(pos, div_neg)};
    const auto np {div(neg, div_pos)};
    const auto nn {div(neg, div_neg)};

    std::cout << " 17 /  5 = " << pp.quot << " remainder " << pp.rem << std::endl;
    std::cout << " 17 / -5 = " << pn.quot << " remainder " << pn.rem << std::endl;
    std::cout << "-17 /  5 = " << np.quot << " remainder " << np.rem << std::endl;
    std::cout << "-17 / -5 = " << nn.quot << " remainder " << nn.rem << std::endl;

    std::cout << "\n--- MIN / -1, the one signed overflow this library defines ---" << std::endl;

    // A built-in int128 division would be undefined here; int256 wraps the
    // quotient back to MIN and the remainder is 0, matching every other
    // two's-complement wrap-around operator in the library.
    constexpr int256 min_value {(std::numeric_limits<int256>::min)()};
    const auto min_by_neg_one {div(min_value, int256{-1})};
    std::cout << "MIN / -1 = " << min_by_neg_one.quot
              << " remainder " << min_by_neg_one.rem << std::endl;
    std::cout << "Quotient equals MIN: " << std::boolalpha << (min_by_neg_one.quot == min_value) << std::endl;

    return 0;
}

Output:

=== div() Function ===
Returns both quotient and remainder from a single division

--- One word divisor ---
1000000000000000000 / 7 = 142857142857142857 remainder 1
Verification: 142857142857142857 * 7 + 1 = 1000000000000000000

--- Powers of two ---
2^200 / 2^100 = 1267650600228229401496703205376 remainder 0

--- Multi word divisor ---
max / 340282366920938463500268095579187314691
  quotient  = 340282366920938463426481119284349108225
  remainder = 73786976294838206460
  quot * divisor + rem == max: true

--- Edge cases ---
3 / 10 = 0 remainder 3
100 / 25 = 4 remainder 0
x / x = 1 remainder 0

=== div() with int256 (returns i256div_t) ===
-100 / 7 = -14 remainder -2

--- Sign Combinations ---
 17 /  5 = 3 remainder 2
 17 / -5 = -3 remainder 2
-17 /  5 = -3 remainder -2
-17 / -5 = 3 remainder -2

--- MIN / -1, the one signed overflow this library defines ---
MIN / -1 = -57896044618658097711785492504343953926634992332820282019728792003956564819968 remainder 0
Quotient equals MIN: true

Use of the library in a CUDA kernel

The type and most of the free functions are annotated to run on the device, so a kernel can use them the same way host code does. This example is the minimal shape of a device workload and a good starting template: allocate managed memory so one array serves both host and device, compute one gcd per thread, then recompute on the host and compare every element.

Example 21. This example demonstrates computing the GCD of 50000 pairs of uint256 values in a CUDA kernel over managed memory, and checking every result against the same computation on the host.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// Computes the GCD of 50000 pairs of uint256 values on a CUDA device and checks every
// result against the same computation on the host. Build with
// nvcc -arch=sm_86 --expt-relaxed-constexpr -DBOOST_INT256_ENABLE_CUDA=1 -I include cuda.cu

#include <boost/int256.hpp>
#include <boost/int256/numeric.hpp>
#include <iostream>
#include <vector>
#include <random>
#include <stdexcept>
#include <cstddef>
#include <cstdint>
#include <cstdlib>

#include <cuda_runtime.h>

using test_type = boost::int256::uint256;

// Calculates the GCD of two values on the device
__global__ void cuda_gcd(const test_type* in1, const test_type* in2, test_type* out, int num_elements)
{
    const int i {static_cast<int>(blockDim.x * blockIdx.x + threadIdx.x)};

    if (i < num_elements)
    {
        out[i] = boost::int256::gcd(in1[i], in2[i]);
    }
}

// Allocates managed space so that an array can be used on both the host and the device
void allocate(test_type** in, int num_elements)
{
    const cudaError_t err {cudaMallocManaged(in, static_cast<std::size_t>(num_elements) * sizeof(test_type))};
    if (err != cudaSuccess)
    {
        throw std::runtime_error(cudaGetErrorString(err));
    }

    cudaDeviceSynchronize();
}

void cleanup(test_type** in1, test_type** in2, test_type** out)
{
    if (*in1 != nullptr)
    {
        cudaFree(*in1);
        *in1 = nullptr;
    }

    if (*in2 != nullptr)
    {
        cudaFree(*in2);
        *in2 = nullptr;
    }

    if (*out != nullptr)
    {
        cudaFree(*out);
        *out = nullptr;
    }

    cudaDeviceReset();
}

int main()
{
    std::mt19937_64 rng {42};

    const int num_elements {50000};
    std::cout << "[Vector operation on " << num_elements << " elements]" << std::endl;

    // Allocate managed space for the inputs and the device outputs, then fill the inputs
    // with random values. One engine draw per 64-bit word.

    test_type* in1 {nullptr};
    test_type* in2 {nullptr};
    test_type* out {nullptr};

    allocate(&in1, num_elements);
    allocate(&in2, num_elements);
    allocate(&out, num_elements);

    for (int i {0}; i < num_elements; ++i)
    {
        in1[i] = test_type{rng(), rng(), rng(), rng()};
        in2[i] = test_type{rng(), rng(), rng(), rng()};
    }

    const int threads_per_block {256};
    const int blocks_per_grid {(num_elements + threads_per_block - 1) / threads_per_block};
    std::cout << "CUDA kernel launch with " << blocks_per_grid << " blocks of "
              << threads_per_block << " threads" << std::endl;

    // Launch the kernel and check for errors

    cuda_gcd<<<blocks_per_grid, threads_per_block>>>(in1, in2, out, num_elements);
    cudaDeviceSynchronize();

    const cudaError_t err {cudaGetLastError()};
    if (err != cudaSuccess)
    {
        std::cerr << "Failed to launch kernel (error code " << cudaGetErrorString(err) << ")!" << std::endl;
        cleanup(&in1, &in2, &out);
        return EXIT_FAILURE;
    }

    // Repeat the same operation on the same inputs on the host

    std::vector<test_type> results;
    results.reserve(static_cast<std::size_t>(num_elements));

    for (int i {0}; i < num_elements; ++i)
    {
        results.emplace_back(boost::int256::gcd(in1[i], in2[i]));
    }

    // The device and the host run the same code, so every element has to agree exactly

    for (int i {0}; i < num_elements; ++i)
    {
        if (out[i] != results[static_cast<std::size_t>(i)])
        {
            std::cerr << "Result verification failed at element: " << i << "!" << std::endl;
            cleanup(&in1, &in2, &out);
            return EXIT_FAILURE;
        }
    }

    cleanup(&in1, &in2, &out);

    std::cout << "All CPU and GPU computed elements match!" << std::endl;

    return 0;
}

Output:

[Vector operation on 50000 elements]
CUDA kernel launch with 196 blocks of 256 threads
All CPU and GPU computed elements match!

The block count is (50000 + 255) / 256, so every line above is the same on any device.

Use of the library in a SYCL kernel

The same workload as the CUDA example above, expressed in SYCL with unified shared memory in place of managed allocations. The one difference in setup is that SYCL device support is opt-in rather than detected, so both the include order and the macro noted below are required: define BOOST_INT256_ENABLE_SYCL and include <sycl/sycl.hpp> before any Boost.Int256 header, then compile with icpx -fsycl.

Example 22. This example demonstrates the same GCD computation in a SYCL kernel over unified shared memory, checked against the host.
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// Computes the GCD of 50000 pairs of uint256 values on a SYCL device and checks every
// result against the same computation on the host. Build with
// icpx -fsycl -fsycl-device-code-split=per_kernel -DBOOST_INT256_ENABLE_SYCL=1 -I include sycl.cpp

#include <sycl/sycl.hpp>
#include <boost/int256.hpp>
#include <boost/int256/numeric.hpp>
#include <iostream>
#include <vector>
#include <random>
#include <cstddef>
#include <cstdint>
#include <cstdlib>

using test_type = boost::int256::uint256;

int main()
{
    std::mt19937_64 rng {42};

    const int num_elements {50000};
    std::cout << "[Vector operation on " << num_elements << " elements]" << std::endl;

    sycl::queue q;
    std::cout << "SYCL device: " << q.get_device().get_info<sycl::info::device::name>() << std::endl;

    // Allocate shared (USM) memory so the arrays are usable on both the host and the device
    test_type* in1 {sycl::malloc_shared<test_type>(num_elements, q)};
    test_type* in2 {sycl::malloc_shared<test_type>(num_elements, q)};
    test_type* out {sycl::malloc_shared<test_type>(num_elements, q)};

    // Fill the inputs with random values, one engine draw per 64-bit word
    for (int i {0}; i < num_elements; ++i)
    {
        in1[i] = test_type{rng(), rng(), rng(), rng()};
        in2[i] = test_type{rng(), rng(), rng(), rng()};
    }

    // Launch the kernel: each work item computes one gcd
    q.submit([&](sycl::handler& h)
    {
        h.parallel_for(sycl::range<1>(num_elements), [=](sycl::id<1> idx)
        {
            const int i {static_cast<int>(idx[0])};
            out[i] = boost::int256::gcd(in1[i], in2[i]);
        });
    }).wait();

    // Repeat the same operation on the same inputs on the host
    std::vector<test_type> results;
    results.reserve(static_cast<std::size_t>(num_elements));
    for (int i {0}; i < num_elements; ++i)
    {
        results.emplace_back(boost::int256::gcd(in1[i], in2[i]));
    }

    // The device and the host run the same code, so every element has to agree exactly
    int ret {EXIT_SUCCESS};
    for (int i {0}; i < num_elements; ++i)
    {
        if (out[i] != results[static_cast<std::size_t>(i)])
        {
            std::cerr << "Result verification failed at element: " << i << "!" << std::endl;
            ret = EXIT_FAILURE;
            break;
        }
    }

    if (ret == EXIT_SUCCESS)
    {
        std::cout << "All CPU and GPU computed elements match!" << std::endl;
    }

    sycl::free(in1, q);
    sycl::free(in2, q);
    sycl::free(out, q);

    return ret;
}

Output:

[Vector operation on 50000 elements]
SYCL device: VirtualApple @ 2.50GHz
All CPU and GPU computed elements match!

The device line reports whichever device the SYCL runtime selected, so that one line differs from machine to machine.