Examples
All the following examples can be found in the examples/ folder of the library.
Basic Construction
Example 1. This example demonstrates the various ways to construct uint128_t and int128_t values
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/literals.hpp>
#include <boost/int128/iostream.hpp>
#include <iostream>
#include <limits>
#include <sstream>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
std::cout << "=== uint128_t Construction ===" << std::endl;
// 1) From a builtin integer type
constexpr uint128_t from_builtin {42U};
std::cout << "From builtin (42U): " << from_builtin << std::endl;
// 2) From high and low 64-bit values (high, low)
constexpr uint128_t from_parts {UINT64_C(0x1), UINT64_C(0x0)}; // 2^64
std::cout << "From parts (1, 0) = 2^64: " << from_parts << std::endl;
constexpr uint128_t max_value {UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF)};
std::cout << "From parts (max, max): " << max_value << std::endl;
std::cout << " Equals numeric_limits max? " << std::boolalpha
<< (max_value == std::numeric_limits<uint128_t>::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::int128::literals;
const auto small_literal {12345_U128};
std::cout << "From literal 12345_U128: " << small_literal << std::endl;
// 4) From macro (like UINT64_C but for 128-bit), good for values that exceed unsigned long long
const auto from_macro {BOOST_INT128_UINT128_C(340282366920938463463374607431768211455)};
std::cout << "From BOOST_INT128_UINT128_C(max): " << from_macro << std::endl;
// 5) From input stream
std::stringstream ss;
ss.str("12345678901234567890123456789");
uint128_t from_stream;
ss >> from_stream;
std::cout << "From stringstream: " << from_stream << std::endl;
std::cout << "\n=== int128_t Construction ===" << std::endl;
// Signed from builtin
constexpr int128_t signed_builtin {-42};
std::cout << "From builtin (-42): " << signed_builtin << std::endl;
// Signed from parts (high is signed, low is unsigned)
constexpr int128_t min_value {INT64_MIN, 0};
std::cout << "From parts (INT64_MIN, 0): " << min_value << std::endl;
std::cout << " Equals numeric_limits min? "
<< (min_value == std::numeric_limits<int128_t>::min()) << 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_i128};
std::cout << "From literal -12345_i128: " << negative_literal << std::endl;
const auto positive_literal {12345_I128};
std::cout << "From literal 12345_I128: " << positive_literal << std::endl;
// For magnitudes beyond unsigned long long you can use the macro or a string literal
const auto large_signed {BOOST_INT128_INT128_C(-99999999999999999999)};
std::cout << "From BOOST_INT128_INT128_C(-99999999999999999999): " << large_signed << std::endl;
const auto large_signed_string {"-99999999999999999999"_i128};
std::cout << "From string literal: " << large_signed_string << std::endl;
// Signed macro
const auto from_signed_macro {BOOST_INT128_INT128_C(-170141183460469231731687303715884105728)};
std::cout << "From BOOST_INT128_INT128_C(min): " << from_signed_macro << std::endl;
std::cout << "\n=== Default and Copy Construction ===" << std::endl;
// Default construction (zero-initialized)
constexpr uint128_t default_constructed {};
std::cout << "Default constructed: " << default_constructed << std::endl;
// Copy construction
const uint128_t copied {from_macro};
std::cout << "Copy constructed: " << 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 uint128_t from_double {12345.9};
std::cout << "uint128_t from 12345.9 (truncated): " << from_double << std::endl;
constexpr int128_t from_negative_double {-12345.9};
std::cout << "int128_t from -12345.9 (truncated toward zero): " << from_negative_double << std::endl;
// Values that exceed the 64-bit range are routed through the full 128-bit decomposition.
const double two_to_the_100 {1.2676506002282294e30}; // 2^100
const uint128_t large_from_double {two_to_the_100};
std::cout << "uint128_t from 2^100: " << large_from_double << std::endl;
std::cout << "\n=== Floating-Point Edge Cases ===" << std::endl;
// NaN yields zero for both signed and unsigned (mirrors libgcc's __fix(uns)Xfti).
const double nan_value {std::numeric_limits<double>::quiet_NaN()};
const uint128_t unsigned_from_nan {nan_value};
const int128_t signed_from_nan {nan_value};
std::cout << "uint128_t from NaN: " << unsigned_from_nan << std::endl;
std::cout << "int128_t from NaN: " << signed_from_nan << std::endl;
// Negative values are clamped to zero when constructing uint128_t.
const uint128_t unsigned_from_negative {-1.0};
std::cout << "uint128_t from -1.0 (clamped to zero): " << unsigned_from_negative << std::endl;
// Positive overflow saturates: anything >= 2^128 (including +infinity) becomes UINT128_MAX.
const double infinity {std::numeric_limits<double>::infinity()};
const uint128_t saturated_unsigned {infinity};
std::cout << "uint128_t from +infinity (saturates to UINT128_MAX): " << saturated_unsigned << std::endl;
// For int128_t, values >= 2^127 saturate to INT128_MAX and values <= -2^127 saturate to INT128_MIN.
const double huge {1e40}; // Well beyond 2^127 (~ 1.7e38)
const int128_t saturated_positive {huge};
const int128_t saturated_negative {-huge};
std::cout << "int128_t from 1e40 (saturates to INT128_MAX): " << saturated_positive << std::endl;
std::cout << "int128_t from -1e40 (saturates to INT128_MIN): " << saturated_negative << std::endl;
return 0;
}
Expected Output
=== uint128_t Construction === From builtin (42U): 42 From parts (1, 0) = 2^64: 18446744073709551616 From parts (max, max): 340282366920938463463374607431768211455 Equals numeric_limits max? true From literal 12345_U128: 12345 From BOOST_INT128_UINT128_C(max): 340282366920938463463374607431768211455 From stringstream: 12345678901234567890123456789 === int128_t Construction === From builtin (-42): -42 From parts (INT64_MIN, 0): -170141183460469231731687303715884105728 Equals numeric_limits min? true From literal -12345_i128: -12345 From literal 12345_I128: 12345 From BOOST_INT128_INT128_C(-99999999999999999999): -99999999999999999999 From string literal: -99999999999999999999 From BOOST_INT128_INT128_C(min): -170141183460469231731687303715884105728 === Default and Copy Construction === Default constructed: 0 Copy constructed: 340282366920938463463374607431768211455 === Floating-Point Construction === uint128_t from 12345.9 (truncated): 12345 int128_t from -12345.9 (truncated toward zero): -12345 uint128_t from 2^100: 1267650600228229401496703205376 === Floating-Point Edge Cases === uint128_t from NaN: 0 int128_t from NaN: 0 uint128_t from -1.0 (clamped to zero): 0 uint128_t from +infinity (saturates to UINT128_MAX): 340282366920938463463374607431768211455 int128_t from 1e40 (saturates to INT128_MAX): 170141183460469231731687303715884105727 int128_t from -1e40 (saturates to INT128_MIN): -170141183460469231731687303715884105728
Basic Arithmetic
Example 2. This example demonstrates arithmetic operations with 128-bit integers
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/iostream.hpp>
#include <iostream>
int main()
{
// The types of this library support all arithmetic operations one would expect.
// They can be between values of the same type, or the same signedness by default.
// See `mixed_type_arithmetic.cpp` for operations with different signedness.
using boost::int128::int128_t;
using boost::int128::uint128_t;
// Basic arithmetic with signed 128-bit integers
int128_t x {1000000000000LL}; // 1 trillion
int128_t y {999999999999LL}; // Just under 1 trillion
std::cout << "=== Signed 128-bit Arithmetic ===" << std::endl;
std::cout << "x = " << x << std::endl;
std::cout << "y = " << y << std::endl;
// Addition and subtraction
std::cout << "\nAddition and Subtraction:" << std::endl;
std::cout << "x + y = " << (x + y) << std::endl;
std::cout << "x - y = " << (x - y) << std::endl;
// Multiplication - results that exceed 64-bit range
std::cout << "\nMultiplication (exceeds 64-bit range):" << std::endl;
std::cout << "x * y = " << (x * y) << std::endl;
// Division and modulo
std::cout << "\nDivision and Modulo:" << std::endl;
std::cout << "x / 7 = " << (x / 7) << std::endl;
std::cout << "x % 7 = " << (x % 7) << std::endl;
// Comparisons
std::cout << "\nComparisons:" << 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;
// Negative values and absolute value
int128_t negative {-42};
std::cout << "\nNegative values:" << std::endl;
std::cout << "negative = " << negative << std::endl;
std::cout << "abs(negative) = " << boost::int128::abs(negative) << std::endl;
// Compound assignment operators
std::cout << "\nCompound assignment operators:" << std::endl;
int128_t z {100};
std::cout << "z = " << z << std::endl;
z += 50;
std::cout << "z += 50: " << z << std::endl;
z -= 25;
std::cout << "z -= 25: " << z << std::endl;
z *= 2;
std::cout << "z *= 2: " << z << std::endl;
z /= 5;
std::cout << "z /= 5: " << z << std::endl;
z %= 7;
std::cout << "z %= 7: " << z << std::endl;
// Unsigned 128-bit arithmetic - useful for very large positive values
std::cout << "\n=== Unsigned 128-bit Arithmetic ===" << std::endl;
uint128_t large {UINT64_C(0x1), UINT64_C(0x0)}; // 2^64
std::cout << "large (2^64) = " << large << std::endl;
std::cout << "large * 2 = " << (large * 2U) << std::endl;
std::cout << "large + large = " << (large + large) << std::endl;
// Increment and decrement
std::cout << "\nIncrement and Decrement:" << std::endl;
int128_t counter {10};
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;
return 0;
}
Expected Output
=== Signed 128-bit Arithmetic === x = 1000000000000 y = 999999999999 Addition and Subtraction: x + y = 1999999999999 x - y = 1 Multiplication (exceeds 64-bit range): x * y = 999999999999000000000000 Division and Modulo: x / 7 = 142857142857 x % 7 = 1 Comparisons: x > y: true x == y: false x != y: true Negative values: negative = -42 abs(negative) = 42 Compound assignment operators: z = 100 z += 50: 150 z -= 25: 125 z *= 2: 250 z /= 5: 50 z %= 7: 1 === Unsigned 128-bit Arithmetic === large (2^64) = 18446744073709551616 large * 2 = 36893488147419103232 large + large = 36893488147419103232 Increment and Decrement: counter = 10 ++counter = 11 counter++ = 11 counter = 12 --counter = 11
IO Streaming
Example 3. This example demonstrates iostream support and string conversions
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/iostream.hpp>
#include <iostream>
#include <sstream>
int main()
{
using boost::int128::int128_t;
using boost::int128::uint128_t;
std::cout << "=== Basic Streaming ===" << std::endl;
// Both types allow streaming as one would expect from a regular builtin-type
constexpr int128_t signed_value {-42};
std::cout << "Signed value: " << signed_value << std::endl;
// We can also use <iomanip> to change the output format
constexpr uint128_t unsigned_value {0x1, UINT64_MAX};
std::cout << "Unsigned value (dec): " << unsigned_value << '\n'
<< "Unsigned value (hex): " << std::hex << unsigned_value << '\n'
<< "Unsigned value (oct): " << std::oct << unsigned_value << std::endl;
// Hex also can be manipulated to be uppercase
std::cout << "Upper unsigned value: " << std::hex << std::uppercase << unsigned_value << std::endl;
// And returned to default formatting
std::cout << "Lower unsigned value: " << std::dec << std::nouppercase << unsigned_value << std::endl;
// Large values that exceed 64-bit range
std::cout << "\n=== Large Values (Beyond 64-bit) ===" << std::endl;
// 2^64 = 18446744073709551616 (first value that doesn't fit in uint64_t)
constexpr uint128_t two_to_64 {1, 0};
std::cout << "2^64 = " << two_to_64 << std::endl;
// 2^100 = a very large number
constexpr uint128_t two_to_100 {uint128_t{1} << 100};
std::cout << "2^100 = " << two_to_100 << std::endl;
// Maximum uint128_t value
constexpr auto uint_max {std::numeric_limits<uint128_t>::max()};
std::cout << "uint128_t max = " << uint_max << std::endl;
// Minimum and maximum int128_t values
constexpr auto int_min {std::numeric_limits<int128_t>::min()};
constexpr auto int_max {std::numeric_limits<int128_t>::max()};
std::cout << "int128_t min = " << int_min << std::endl;
std::cout << "int128_t max = " << int_max << std::endl;
// String conversion using stringstream
std::cout << "\n=== String Conversion with std::stringstream ===" << std::endl;
// Convert uint128_t to string
std::ostringstream oss;
oss << two_to_100;
auto str {oss.str()};
std::cout << "uint128_t to string: \"" << str << "\"" << std::endl;
// Convert string to uint128_t
std::istringstream iss {"123456789012345678901234567890"};
uint128_t parsed_value {};
iss >> parsed_value;
std::cout << "String to uint128_t: " << parsed_value << std::endl;
// Round-trip: value -> string -> value
std::cout << "\n=== Round-trip Conversion ===" << std::endl;
constexpr uint128_t original {0xDEADBEEF, 0xCAFEBABE12345678};
std::ostringstream oss2;
oss2 << original;
auto original_str {oss2.str()};
std::istringstream iss2 {original_str};
uint128_t 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;
return 0;
}
Expected Output
=== Basic Streaming === Signed value: -42 Unsigned value (dec): 36893488147419103231 Unsigned value (hex): 1ffffffffffffffff Unsigned value (oct): 3777777777777777777777 Upper unsigned value: 1FFFFFFFFFFFFFFFF Lower unsigned value: 36893488147419103231 === Large Values (Beyond 64-bit) === 2^64 = 18446744073709551616 2^100 = 1267650600228229401496703205376 uint128_t max = 340282366920938463463374607431768211455 int128_t min = -170141183460469231731687303715884105728 int128_t max = 170141183460469231731687303715884105727 === String Conversion with std::stringstream === uint128_t to string: "1267650600228229401496703205376" String to uint128_t: 123456789012345678901234567890 === Round-trip Conversion === Original: 68915718020162848918556923512 As string: "68915718020162848918556923512" Round-tripped: 68915718020162848918556923512 Match: true
Rollover Behavior
Example 4. This example demonstrates the rollover behavior of both the unsigned and signed type
// Copyright 2025 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 for both signed and unsigned int128
#include <boost/int128.hpp>
#include <iostream>
#include <iomanip>
#include <limits>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
constexpr uint128_t max_unsigned_value {std::numeric_limits<uint128_t>::max()};
constexpr uint128_t min_unsigned_value {std::numeric_limits<uint128_t>::min()};
std::cout << "=== uint128_t behavior ===" << std::endl;
std::cout << "Max of uint128_t: " << max_unsigned_value << '\n'
<< "Max + 1U: " << max_unsigned_value + 1U << "\n\n";
std::cout << "Min of uint128_t: " << min_unsigned_value << '\n'
<< "Min - 1U: " << min_unsigned_value - 1U << "\n\n";
constexpr int128_t max_signed_value {std::numeric_limits<int128_t>::max()};
constexpr int128_t min_signed_value {std::numeric_limits<int128_t>::min()};
std::cout << "=== int128_t behavior ===" << std::endl;
std::cout << "Max of int128_t: " << max_signed_value << '\n'
<< "Max + 1: " << max_signed_value + 1 << "\n";
std::cout << "\nMin of int128_t: " << min_signed_value << '\n'
<< "Min - 1: " << min_signed_value - 1 << '\n' << std::endl;
}
Expected Output
=== uint128_t behavior === Max of uint128_t: 340282366920938463463374607431768211455 Max + 1U: 0 Min of uint128_t: 0 Min - 1U: 340282366920938463463374607431768211455 === int128_t behavior === Max of int128_t: 170141183460469231731687303715884105727 Max + 1: -170141183460469231731687303715884105728 Min of int128_t: -170141183460469231731687303715884105728 Min - 1: 170141183460469231731687303715884105727
Bitwise Functions (<bit>)
Example 5. This example demonstrates bitwise operations from the <bit> header
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// Individual headers
#include <boost/int128/int128.hpp>
#include <boost/int128/bit.hpp>
// Or you can do a single header
// #include <boost/int128.hpp>
int main()
{
// The functions from bit are only available for uint128_t
constexpr boost::int128::uint128_t x {1U};
// All the functions are constexpr
// Does the value have only a single bit set?
static_assert(boost::int128::has_single_bit(x), "Should have one bit");
// How many zeros from the left
static_assert(boost::int128::countl_zero(x) == 127U, "Should be 127");
// The bit width of the value
// 1 + 1 is 10 in binary which is 2 bits wide
static_assert(boost::int128::bit_width(x + x) == 2U, "2 bits wide");
// The smallest power of two not greater than the input value
static_assert(boost::int128::bit_floor(3U * x) == 2U, "2 < 3");
// The smallest power of two not Smaller than the input value
static_assert(boost::int128::bit_ceil(5U * x) == 8U, "8 > 5");
// How many zeros from the right?
static_assert(boost::int128::countr_zero(2U * x) == 1, "1 zero to the right of 10");
// How many 1-bits in the value
static_assert(boost::int128::popcount(7U * x) == 3, "111");
// Swap the bytes
// Create a value with distinct byte pattern
constexpr boost::int128::uint128_t original{
0x0123456789ABCDEFULL,
0xFEDCBA9876543210ULL
};
// Expected result after byteswap
constexpr boost::int128::uint128_t expected{
0x1032547698BADCFEULL,
0xEFCDAB8967452301ULL
};
static_assert(boost::int128::byteswap(original) == expected, "Mismatched byteswap");
static_assert(boost::int128::byteswap(expected) == original, "Mismatched byteswap");
return 0;
}
Saturating Arithmetic (<numeric>)
Example 6. This example demonstrates saturating arithmetic operations that clamp to min/max instead of overflowing
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// Individual headers
#include <boost/int128/int128.hpp>
#include <boost/int128/numeric.hpp>
#include <boost/int128/iostream.hpp>
// Or you can do a single header
// #include <boost/int128.hpp>
#include <limits>
#include <type_traits>
#include <iostream>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
// std::numeric_limits is overloaded for both types
constexpr auto uint_max {std::numeric_limits<uint128_t>::max()};
static_assert(std::is_same<decltype(uint_max), const uint128_t>::value, "Types should match");
constexpr auto int_max {std::numeric_limits<int128_t>::max()};
constexpr auto int_min {std::numeric_limits<int128_t>::min()};
std::cout << "=== Saturating Arithmetic ===" << std::endl;
std::cout << "uint128_t max = " << uint_max << std::endl;
std::cout << "int128_t max = " << int_max << std::endl;
std::cout << "int128_t min = " << int_min << std::endl;
// Saturating arithmetic returns max on overflow, or min on underflow rather than rolling over
std::cout << "\n=== Saturating Addition and Subtraction ===" << std::endl;
std::cout << "add_sat(uint_max, uint_max) = " << boost::int128::add_sat(uint_max, uint_max)
<< " (saturates to uint_max)" << std::endl;
std::cout << "sub_sat(0, uint_max) = " << boost::int128::sub_sat(uint128_t{0}, uint_max)
<< " (saturates to 0, not underflow)" << std::endl;
// This is especially useful for signed types since rollover is undefined
std::cout << "\n=== Saturating Multiplication ===" << std::endl;
std::cout << "mul_sat(int_max, 2) = " << boost::int128::mul_sat(int_max, 2)
<< " (saturates to int_max)" << std::endl;
std::cout << "mul_sat(-(int_max - 2), 5) = " << boost::int128::mul_sat(-(int_max - 2), 5)
<< " (saturates to int_min)" << std::endl;
// The only case in the library where div_sat overflows is x = int_min and y = -1
std::cout << "\n=== Saturating Division ===" << std::endl;
std::cout << "div_sat(int_min, -1) = " << boost::int128::div_sat(int_min, -1)
<< " (saturates to int_max; normally this overflows)" << std::endl;
// saturate_cast allows types to be safely converted without rollover behavior
std::cout << "\n=== Saturating Casts ===" << std::endl;
std::cout << "saturate_cast<int128_t>(uint_max) = " << boost::int128::saturate_cast<int128_t>(uint_max)
<< " (saturates to int_max)" << std::endl;
// You can also cast to builtin types
std::cout << "saturate_cast<int64_t>(int_max) = " << boost::int128::saturate_cast<std::int64_t>(int_max)
<< " (saturates to INT64_MAX)" << std::endl;
// Even of different signedness as this is treated like a static cast
std::cout << "saturate_cast<int32_t>(uint_max) = " << boost::int128::saturate_cast<std::int32_t>(uint_max)
<< " (saturates to INT32_MAX)" << std::endl;
return 0;
}
Expected Output
=== Saturating Arithmetic === uint128_t max = 340282366920938463463374607431768211455 int128_t max = 170141183460469231731687303715884105727 int128_t min = -170141183460469231731687303715884105728 === Saturating Addition and Subtraction === add_sat(uint_max, uint_max) = 340282366920938463463374607431768211455 (saturates to uint_max) sub_sat(0, uint_max) = 0 (saturates to 0, not underflow) === Saturating Multiplication === mul_sat(int_max, 2) = 170141183460469231731687303715884105727 (saturates to int_max) mul_sat(-(int_max - 2), 5) = -170141183460469231731687303715884105728 (saturates to int_min) === Saturating Division === div_sat(int_min, -1) = 170141183460469231731687303715884105727 (saturates to int_max; normally this overflows) === Saturating Casts === saturate_cast<int128_t>(uint_max) = 170141183460469231731687303715884105727 (saturates to int_max) saturate_cast<int64_t>(int_max) = 9223372036854775807 (saturates to INT64_MAX) saturate_cast<int32_t>(uint_max) = 2147483647 (saturates to INT32_MAX)
Numeric Algorithms (<numeric>)
Example 7. This example demonstrates gcd, lcm, and midpoint algorithms
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/numeric.hpp>
#include <boost/int128/iostream.hpp>
#include <iostream>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
std::cout << "=== Greatest Common Divisor (gcd) ===" << std::endl;
// Basic gcd
constexpr uint128_t a {48};
constexpr uint128_t b {18};
std::cout << "gcd(" << a << ", " << b << ") = " << boost::int128::gcd(a, b) << std::endl;
// gcd with larger values
constexpr uint128_t large_a {123456789012345678ULL};
constexpr uint128_t large_b {987654321098765432ULL};
std::cout << "gcd(" << large_a << ", " << large_b << ") = "
<< boost::int128::gcd(large_a, large_b) << std::endl;
// gcd with 128-bit values
constexpr uint128_t huge_a {uint128_t{1} << 100};
constexpr uint128_t huge_b {uint128_t{1} << 80};
std::cout << "gcd(2^100, 2^80) = " << boost::int128::gcd(huge_a, huge_b) << " (= 2^80)" << std::endl;
// Signed gcd (always returns positive)
constexpr int128_t neg_a {-48};
constexpr int128_t neg_b {18};
std::cout << "gcd(" << neg_a << ", " << neg_b << ") = "
<< boost::int128::gcd(neg_a, neg_b) << " (always positive)" << std::endl;
std::cout << "\n=== Least Common Multiple (lcm) ===" << std::endl;
// Basic lcm
constexpr uint128_t x {12};
constexpr uint128_t y {18};
std::cout << "lcm(" << x << ", " << y << ") = " << boost::int128::lcm(x, y) << std::endl;
// lcm with coprime numbers
constexpr uint128_t p {7};
constexpr uint128_t q {11};
std::cout << "lcm(" << p << ", " << q << ") = " << boost::int128::lcm(p, q)
<< " (coprime: lcm = p * q)" << std::endl;
// Relationship: gcd(a,b) * lcm(a,b) = a * b
std::cout << "\nVerifying gcd * lcm = a * b:" << std::endl;
auto g {boost::int128::gcd(x, y)};
auto l {boost::int128::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;
// Unsigned midpoint
constexpr uint128_t low {10};
constexpr uint128_t high {20};
std::cout << "midpoint(" << low << ", " << high << ") = "
<< boost::int128::midpoint(low, high) << std::endl;
// Midpoint with odd sum (rounds toward first argument)
constexpr uint128_t odd_low {10};
constexpr uint128_t odd_high {21};
std::cout << "midpoint(" << odd_low << ", " << odd_high << ") = "
<< boost::int128::midpoint(odd_low, odd_high) << " (rounds toward first arg)" << std::endl;
std::cout << "midpoint(" << odd_high << ", " << odd_low << ") = "
<< boost::int128::midpoint(odd_high, odd_low) << " (rounds toward first arg)" << std::endl;
// Midpoint avoids overflow (unlike (a+b)/2)
std::cout << "\n--- Overflow-safe midpoint ---" << std::endl;
constexpr auto uint_max {std::numeric_limits<uint128_t>::max()};
constexpr auto uint_max_minus_10 {uint_max - 10U};
std::cout << "midpoint(uint128_max, uint128_max - 10) = "
<< boost::int128::midpoint(uint_max, uint_max_minus_10) << std::endl;
std::cout << "(This would overflow if computed as (a + b) / 2)" << std::endl;
// Signed midpoint
std::cout << "\n--- Signed midpoint ---" << std::endl;
constexpr int128_t neg {-100};
constexpr int128_t pos {100};
std::cout << "midpoint(" << neg << ", " << pos << ") = "
<< boost::int128::midpoint(neg, pos) << std::endl;
constexpr int128_t neg2 {-100};
constexpr int128_t neg3 {-50};
std::cout << "midpoint(" << neg2 << ", " << neg3 << ") = "
<< boost::int128::midpoint(neg2, neg3) << std::endl;
return 0;
}
Expected Output
=== Greatest Common Divisor (gcd) === gcd(48, 18) = 6 gcd(123456789012345678, 987654321098765432) = 2 gcd(2^100, 2^80) = 1208925819614629174706176 (= 2^80) gcd(-48, 18) = 6 (always positive) === Least Common Multiple (lcm) === lcm(12, 18) = 36 lcm(7, 11) = 77 (coprime: lcm = p * q) 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(uint128_max, uint128_max - 10) = 340282366920938463463374607431768211450 (This would overflow if computed as (a + b) / 2) --- Signed midpoint --- midpoint(-100, 100) = 0 midpoint(-100, -50) = -75
Checked Arithmetic
Example 8. This example demonstrates checked addition, subtraction, and multiplication following the C23 checked-integer contract
// 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/int128/utilities.hpp>
#include <boost/int128/iostream.hpp>
// Or you can do a single header
// #include <boost/int128.hpp>
#include <cstdint>
#include <limits>
#include <iostream>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
using boost::int128::ckd_add;
using boost::int128::ckd_sub;
using boost::int128::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 u_max {std::numeric_limits<uint128_t>::max()};
constexpr auto i_max {std::numeric_limits<int128_t>::max()};
constexpr auto i_min {std::numeric_limits<int128_t>::min()};
// A result that fits returns false and holds the exact value.
std::cout << "=== Results That Fit ===" << std::endl;
int128_t r {};
bool overflow {ckd_add(&r, int128_t{20}, int128_t{22})};
std::cout << "ckd_add(20, 22): overflow=" << overflow << ", result=" << r << std::endl;
// Addition that exceeds the type wraps modulo 2^128 and reports overflow.
std::cout << "\n=== Addition Overflow ===" << std::endl;
uint128_t u {};
overflow = ckd_add(&u, u_max, uint128_t{1});
std::cout << "ckd_add(UINT128_MAX, 1): overflow=" << overflow << ", wrapped=" << u << 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(&u, uint128_t{0}, uint128_t{1});
std::cout << "ckd_sub(0, 1): overflow=" << overflow << ", wrapped=" << u << std::endl;
// Multiplication detects overflow that operator* would silently roll over,
// including INT128_MIN * -1, whose true result is not representable.
std::cout << "\n=== Multiplication Overflow ===" << std::endl;
overflow = ckd_mul(&r, i_max, int128_t{2});
std::cout << "ckd_mul(INT128_MAX, 2): overflow=" << overflow << ", wrapped=" << r << std::endl;
overflow = ckd_mul(&r, i_min, int128_t{-1});
std::cout << "ckd_mul(INT128_MIN, -1): overflow=" << overflow << ", wrapped=" << r << 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::int64_t narrow {};
overflow = ckd_add(&narrow, uint128_t{5}, int128_t{-3});
std::cout << "ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=" << overflow
<< ", result=" << narrow << 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;
return 0;
}
Expected Output
=== Results That Fit ===
ckd_add(20, 22): overflow=false, result=42
=== Addition Overflow ===
ckd_add(UINT128_MAX, 1): overflow=true, wrapped=0
=== Subtraction Underflow ===
ckd_sub(0, 1): overflow=true, wrapped=340282366920938463463374607431768211455
=== Multiplication Overflow ===
ckd_mul(INT128_MAX, 2): overflow=true, wrapped=-2
ckd_mul(INT128_MIN, -1): overflow=true, wrapped=-170141183460469231731687303715884105728
=== Mixed Types ===
ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=false, result=2
ckd_mul<uint8_t>(20, 20): overflow=true, wrapped=144
Mixed Signedness Arithmetic
Example 9. This example demonstrates arithmetic between 128-bit and built-in integer types
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128.hpp>
#include <iostream>
int main()
{
// Mixed-sign comparisons and arithmetic between int128_t, uint128_t, and
// built-in integer types of opposite signedness follow the C++ usual
// arithmetic conversions, identical to the built-in __int128 /
// unsigned __int128 types.
std::cout << "=== Mixed Type Arithmetic with uint128_t ===" << std::endl;
constexpr boost::int128::uint128_t unsigned_value {3};
std::cout << "unsigned_value = " << unsigned_value << std::endl;
constexpr auto greater_unsigned_value {unsigned_value + 5};
std::cout << "unsigned_value + 1 = " << (unsigned_value + 1) << std::endl;
std::cout << "unsigned_value - 1 = " << (unsigned_value - 1) << std::endl;
std::cout << "unsigned_value * 2 = " << (unsigned_value * 2) << std::endl;
std::cout << "unsigned_value / 3 = " << (unsigned_value / 3) << std::endl;
std::cout << "unsigned_value % 3 = " << (unsigned_value % 3) << std::endl;
std::cout << "unsigned_value + 5 = " << (unsigned_value + 5)
<< " (same as greater_unsigned_value: " << greater_unsigned_value << ")" << std::endl;
std::cout << "\n=== Mixed Type Arithmetic with int128_t ===" << std::endl;
constexpr boost::int128::int128_t signed_value {-3};
std::cout << "signed_value = " << signed_value << std::endl;
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;
return 0;
}
Expected Output
=== Mixed Type Arithmetic with uint128_t === unsigned_value = 3 unsigned_value + 1 = 4 unsigned_value - 1 = 2 unsigned_value * 2 = 6 unsigned_value / 3 = 1 unsigned_value % 3 = 0 unsigned_value + 5 = 8 (same as greater_unsigned_value: 8) === Mixed Type Arithmetic with int128_t === signed_value = -3 signed_value + 1U = -2 signed_value - 4U = -7 signed_value * 2 = -6 signed_value / 4U = 0
Boost.Math and Boost.Random Integration
Example 10. This example demonstrates integration with Boost.Math and Boost.Random libraries
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128.hpp>
#include <boost/int128/random.hpp> // Not included in the convenience header, but needed for boost.random interop
#include <boost/math/statistics/univariate_statistics.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <iostream>
#include <limits>
#include <array>
#include <random>
int main()
{
std::cout << "=== uint128_t ===" << '\n';
// Setup our rng and distribution
std::mt19937_64 rng {42};
boost::random::uniform_int_distribution<boost::int128::uint128_t> dist {0, (std::numeric_limits<boost::int128::uint128_t>::max)()};
// Create a dataset for ourselves of random uint128_ts using our dist and rng from above
std::array<boost::int128::uint128_t, 10000> data_set;
for (auto& value : data_set)
{
value = dist(rng);
}
// Perform some rudimentary statistical analysis on our dataset
std::cout << " Mean: " << boost::math::statistics::mean(data_set) << '\n';
std::cout << "Variance: " << boost::math::statistics::variance(data_set) << '\n';
std::cout << " Median: " << boost::math::statistics::median(data_set) << '\n';
std::cout << "=== int128_t ===" << '\n';
// We can also generate random signed integers using int128_t
boost::random::uniform_int_distribution<boost::int128::int128_t> signed_dist {std::numeric_limits<boost::int128::int128_t>::min(), std::numeric_limits<boost::int128::int128_t>::max()};
std::cout << "Random int128_t: " << signed_dist(rng) << std::endl;
return 0;
}
Example Output (values vary per run)
=== uint128_t ===
Mean: 22125900135088040520646253247977468
Variance: 15183108029620265677746188314852225
Median: 169775281866460752209725324063124732284
=== int128_t ===
Random int128_t: 45422201008201503618595888886744218664
Boost.Charconv Integration
Example 11. This example demonstrates fast string-to-number and number-to-string conversions using Boost.Charconv
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/iostream.hpp>
#include <boost/int128/charconv.hpp>
#include <boost/charconv.hpp>
#include <iostream>
#include <limits>
#include <cstring>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
char buffer[64];
// === to_chars: Convert integers to character strings ===
std::cout << "=== to_chars ===" << std::endl;
// Unsigned 128-bit to decimal string
constexpr uint128_t max_u128 {std::numeric_limits<uint128_t>::max()};
auto result {boost::charconv::to_chars(buffer, buffer + sizeof(buffer), max_u128)};
*result.ptr = '\0';
std::cout << "uint128_t max (decimal): " << buffer << std::endl;
// Signed 128-bit to decimal string
constexpr int128_t min_i128 {std::numeric_limits<int128_t>::min()};
result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), min_i128);
*result.ptr = '\0';
std::cout << "int128_t min (decimal): " << buffer << std::endl;
// Hexadecimal output (base 16)
uint128_t hex_value {UINT64_C(0xDEADBEEF), UINT64_C(0xCAFEBABE12345678)};
result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), hex_value, 16);
*result.ptr = '\0';
std::cout << "uint128_t (hex): 0x" << buffer << std::endl;
// Octal output (base 8)
result = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), int128_t{511}, 8);
*result.ptr = '\0';
std::cout << "int128_t 511 (octal): 0" << buffer << std::endl;
// === from_chars: Parse character strings to integers ===
std::cout << "\n=== from_chars ===" << std::endl;
// Parse decimal string to uint128_t
const char* decimal_str {"340282366920938463463374607431768211455"};
uint128_t parsed_unsigned;
boost::charconv::from_chars(decimal_str, decimal_str + std::strlen(decimal_str), parsed_unsigned);
std::cout << "Parsed \"" << decimal_str << "\"" << std::endl;
std::cout << " Result: " << parsed_unsigned << std::endl;
std::cout << " Equals max? " << std::boolalpha << (parsed_unsigned == max_u128) << std::endl;
// Parse negative decimal string to int128_t
const char* negative_str {"-170141183460469231731687303715884105728"};
int128_t parsed_signed;
boost::charconv::from_chars(negative_str, negative_str + std::strlen(negative_str), parsed_signed);
std::cout << "Parsed \"" << negative_str << "\"" << std::endl;
std::cout << " Result: " << parsed_signed << std::endl;
std::cout << " Equals min? " << (parsed_signed == min_i128) << std::endl;
// Parse hexadecimal string (base 16)
const char* hex_str {"DEADBEEFCAFEBABE12345678"};
uint128_t parsed_hex;
boost::charconv::from_chars(hex_str, hex_str + std::strlen(hex_str), parsed_hex, 16);
std::cout << "Parsed hex \"" << hex_str << "\"" << std::endl;
std::cout << " Result: " << parsed_hex << std::endl;
return 0;
}
Expected Output
=== to_chars === uint128_t max (decimal): 340282366920938463463374607431768211455 int128_t min (decimal): -170141183460469231731687303715884105728 uint128_t (hex): 0xdeadbeefcafebabe12345678 int128_t 511 (octal): 0777 === from_chars === Parsed "340282366920938463463374607431768211455" Result: 340282366920938463463374607431768211455 Equals max? true Parsed "-170141183460469231731687303715884105728" Result: -170141183460469231731687303715884105728 Equals min? true Parsed hex "DEADBEEFCAFEBABE12345678" Result: 68915718020162848918556923512
String Conversion (to_string)
Example 12. This example demonstrates to_string for 128-bit integers, comparing results against std::to_string for values that fit in 64 bits
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/string.hpp>
#include <boost/int128/literals.hpp>
#include <iostream>
#include <string>
#include <cstdint>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
using boost::int128::to_string;
using namespace boost::int128::literals;
std::cout << "=== to_string with uint128_t ===" << std::endl;
// Compare against std::to_string for values that fit in 64 bits
constexpr uint128_t u_small {UINT64_C(1234567890)};
const auto u_small_str {to_string(u_small)};
const auto u_small_std {std::to_string(std::uint64_t{1234567890})};
std::cout << "uint128_t to_string(1234567890): " << u_small_str << std::endl;
std::cout << "std::to_string(uint64_t 1234567890): " << u_small_std << std::endl;
std::cout << "Match: " << std::boolalpha << (u_small_str == u_small_std) << std::endl;
constexpr uint128_t u_max64 {UINT64_MAX};
const auto u_max64_str {to_string(u_max64)};
const auto u_max64_std {std::to_string(UINT64_MAX)};
std::cout << "\nuint128_t to_string(UINT64_MAX): " << u_max64_str << std::endl;
std::cout << "std::to_string(UINT64_MAX): " << u_max64_std << std::endl;
std::cout << "Match: " << (u_max64_str == u_max64_std) << std::endl;
// Values beyond 64-bit range
const auto large_unsigned {340282366920938463463374607431768211455_U128};
std::cout << "\nuint128_t max: " << to_string(large_unsigned) << std::endl;
std::cout << "\n=== to_string with int128_t ===" << std::endl;
// Compare against std::to_string for values that fit in 64 bits
constexpr int128_t 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 << "int128_t 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 int128_t 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 << "\nint128_t to_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;
// Values beyond 64-bit range.
// The positive magnitude 2^127 exceeds INT128_MAX, so INT128_MIN cannot be
// written as a negated _i128 literal; this line therefore yields 0. Use the
// INT128_C macro (below) or std::numeric_limits<int128_t>::min() instead.
const auto large_negative {-170141183460469231731687303715884105728_i128};
std::cout << "\nint128_t min with string literal: " << to_string(large_negative) << std::endl;
const auto large_negative_c {BOOST_INT128_INT128_C(-170141183460469231731687303715884105728)};
std::cout << "\nint128_t min with INT128_C macro: " << to_string(large_negative_c) << std::endl;
const auto large_positive {std::numeric_limits<int128_t>::max()};
std::cout << "int128_t max: " << to_string(large_positive) << std::endl;
return 0;
}
Expected Output
=== to_string with uint128_t === uint128_t to_string(1234567890): 1234567890 std::to_string(uint64_t 1234567890): 1234567890 Match: true uint128_t to_string(UINT64_MAX): 18446744073709551615 std::to_string(UINT64_MAX): 18446744073709551615 Match: true uint128_t max: 340282366920938463463374607431768211455 === to_string with int128_t === int128_t to_string(-42): -42 std::to_string(int64_t -42): -42 Match: true int128_t to_string(INT64_MAX): 9223372036854775807 std::to_string(INT64_MAX): 9223372036854775807 Match: true int128_t min with string literal: 0 int128_t min with INT128_C macro: -170141183460469231731687303715884105728 int128_t max: 170141183460469231731687303715884105727
The line int128_t min with string literal: 0 is expected. The positive magnitude of the minimum int128_t value (2^127) is larger than the maximum int128_t value, so it cannot be written as a negated _i128 literal. Construct it with BOOST_INT128_INT128_C or (std::numeric_limits<int128_t>::min)(), as the example shows.
|
{fmt} Library Integration
Example 13. This example demonstrates formatting with the {fmt} library including alignment specifiers
// Copyright 2025 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 int128 types.
// Requires {fmt} to be installed: https://github.com/fmtlib/fmt
//
// For C++20 std::format support, use <boost/int128/format.hpp> instead,
// which provides the same formatting capabilities with std::format.
#include <boost/int128/int128.hpp>
#include <boost/int128/fmt_format.hpp>
#include <fmt/format.h>
#include <iostream>
int main()
{
using boost::int128::int128_t;
using boost::int128::uint128_t;
std::cout << "=== Basic Formatting ===" << std::endl;
constexpr uint128_t unsigned_value {0xDEADBEEF, 0xCAFEBABE12345678};
constexpr int128_t signed_value {-123456789012345678};
// Default decimal formatting
std::cout << fmt::format("Default (decimal): {}", unsigned_value) << std::endl;
std::cout << fmt::format("Signed value: {}", signed_value) << std::endl;
std::cout << "\n=== Base Specifiers ===" << std::endl;
// Different bases: binary, octal, decimal, hex
constexpr uint128_t value {255};
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;
constexpr int128_t positive {42};
constexpr int128_t negative {-42};
// Sign specifiers: + (always show), - (default), space (space for positive)
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=== 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}'", positive) << std::endl;
std::cout << fmt::format("Right align: '{:>10}'", positive) << std::endl;
std::cout << fmt::format("Center align: '{:^10}'", positive) << std::endl;
std::cout << "\n=== Alignment with Fill Characters ===" << std::endl;
// Custom fill characters
std::cout << fmt::format("Left with *: '{:*<10}'", positive) << std::endl;
std::cout << fmt::format("Right with 0: '{:0>10}'", positive) << std::endl;
std::cout << fmt::format("Center with -: '{:-^10}'", positive) << std::endl;
std::cout << "\n=== Alignment with Sign ===" << std::endl;
// Alignment combined with sign specifiers
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=== 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 64-bit range
constexpr auto uint_max {std::numeric_limits<uint128_t>::max()};
constexpr auto int_min {std::numeric_limits<int128_t>::min()};
std::cout << fmt::format("uint128_t max: {}", uint_max) << std::endl;
std::cout << fmt::format("uint128_t max (hex): {:#x}", uint_max) << std::endl;
std::cout << fmt::format("int128_t min: {}", int_min) << std::endl;
std::cout << "\n=== Combined Format Specifiers ===" << std::endl;
// Combining multiple specifiers
std::cout << fmt::format("Hex with prefix, uppercase, padded: {:#016X}", unsigned_value) << std::endl;
std::cout << fmt::format("Signed with plus, padded: {:+020}", signed_value) << std::endl;
return 0;
}
Expected Output
=== Basic Formatting === Default (decimal): 68915718020162848918556923512 Signed value: -123456789012345678 === 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: +42 and -42 Minus only: 42 and -42 Space sign: 42 and -42 === Zero Padding === 8-digit padding: 00000255 16-digit padding: 0000000000000255 === Alignment === Left align: '42 ' Right align: ' 42' Center align: ' 42 ' === Alignment with Fill Characters === Left with *: '42********' Right with 0: '0000000042' Center with -: '----42----' === Alignment with Sign === Right align +: ' +42' Left align +: '+42 ' Center align +: ' +42 ' Right align -: '*******-42' === Alignment with Hex and Prefix === Right align hex: ' ff' Left align hex: 'ff ' Center with prefix: '****0xff****' === Large Values === uint128_t max: 340282366920938463463374607431768211455 uint128_t max (hex): 0xffffffffffffffffffffffffffffffff int128_t min: -170141183460469231731687303715884105728 === Combined Format Specifiers === Hex with prefix, uppercase, padded: 0XDEADBEEFCAFEBABE12345678 Signed with plus, padded: -00123456789012345678
Division with Remainder (<cstdlib>)
Example 14. This example demonstrates the div() function that returns both quotient and remainder
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/int128/int128.hpp>
#include <boost/int128/cstdlib.hpp>
#include <boost/int128/iostream.hpp>
#include <iostream>
int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
std::cout << "=== div() Function ===" << std::endl;
std::cout << "Returns both quotient and remainder in a single operation" << std::endl;
// Unsigned division
std::cout << "\n--- Unsigned Division ---" << std::endl;
constexpr uint128_t dividend {1000000000000000000ULL};
constexpr uint128_t divisor {7};
auto uresult {boost::int128::div(dividend, divisor)};
std::cout << dividend << " / " << divisor << " = " << uresult.quot
<< " remainder " << uresult.rem << std::endl;
// Verify: quot * divisor + rem == dividend
std::cout << "Verification: " << uresult.quot << " * " << divisor
<< " + " << uresult.rem << " = " << (uresult.quot * divisor + uresult.rem) << std::endl;
// Large value division
std::cout << "\n--- Large Value Division ---" << std::endl;
constexpr uint128_t large_dividend {uint128_t{1} << 100}; // 2^100
constexpr uint128_t large_divisor {uint128_t{1} << 50}; // 2^50
auto large_result {boost::int128::div(large_dividend, large_divisor)};
std::cout << "2^100 / 2^50 = " << large_result.quot
<< " remainder " << large_result.rem << std::endl;
// Signed division
std::cout << "\n--- Signed Division ---" << std::endl;
constexpr int128_t signed_dividend {-100};
constexpr int128_t signed_divisor {7};
auto sresult {boost::int128::div(signed_dividend, signed_divisor)};
std::cout << signed_dividend << " / " << signed_divisor << " = " << sresult.quot
<< " remainder " << sresult.rem << std::endl;
// Different sign combinations
std::cout << "\n--- Sign Combinations ---" << std::endl;
constexpr int128_t pos {17};
constexpr int128_t neg {-17};
constexpr int128_t div_pos {5};
constexpr int128_t div_neg {-5};
auto pp {boost::int128::div(pos, div_pos)};
auto pn {boost::int128::div(pos, div_neg)};
auto np {boost::int128::div(neg, div_pos)};
auto nn {boost::int128::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;
// Edge case: dividend smaller than divisor
std::cout << "\n--- Edge Cases ---" << std::endl;
auto small_div {boost::int128::div(uint128_t{3}, uint128_t{10})};
std::cout << "3 / 10 = " << small_div.quot << " remainder " << small_div.rem << std::endl;
return 0;
}
Expected Output
=== div() Function === Returns both quotient and remainder in a single operation --- Unsigned Division --- 1000000000000000000 / 7 = 142857142857142857 remainder 1 Verification: 142857142857142857 * 7 + 1 = 1000000000000000000 --- Large Value Division --- 2^100 / 2^50 = 1125899906842624 remainder 0 --- Signed Division --- -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 --- Edge Cases --- 3 / 10 = 0 remainder 3
CUDA Usage
Example 15. This example demonstrates how to use library types and functions inside a CUDA kernel.
// Copyright Matt Borland 2026.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <iostream>
#include <vector>
#include <random>
#include <limits>
#include <boost/int128.hpp>
#include <boost/int128/numeric.hpp>
#include <boost/int128/random.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <cuda_runtime.h>
using test_type = boost::int128::uint128_t;
// Calculates the GCD of 2 values on device
__global__ void cuda_gcd(const test_type* in1, const test_type* in2, test_type* out, int numElements)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements)
{
out[i] = boost::int128::gcd(in1[i], in2[i]);
}
}
// Allocate managed space so that the arrays can be used on both host and device
void allocate(test_type** in, int numElements)
{
cudaError_t err = cudaSuccess;
err = cudaMallocManaged(in, numElements * 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 numElements = 50000;
std::cout << "[Vector operation on " << numElements << " elements]" << std::endl;
// Allocate managed space for our inputs and GPU outputs
// We then fill them with random numbers
test_type* in1 = nullptr;
test_type* in2 = nullptr;
test_type* out = nullptr;
allocate(&in1, numElements);
allocate(&in2, numElements);
allocate(&out, numElements);
boost::random::uniform_int_distribution<test_type> dist {(std::numeric_limits<test_type>::min)(), (std::numeric_limits<test_type>::max)()};
for (std::size_t i = 0; i < numElements; ++i)
{
in1[i] = dist(rng);
in2[i] = dist(rng);
}
const int threadsPerBlock = 256;
const int blocksPerGrid = (numElements + threadsPerBlock - 1) / threadsPerBlock;
std::cout << "CUDA kernel launch with " << blocksPerGrid << " blocks of " << threadsPerBlock << " threads" << std::endl;
// Launch the CUDA kernel and check for errors
cuda_gcd<<<blocksPerGrid, threadsPerBlock>>>(in1, in2, out, numElements);
cudaDeviceSynchronize();
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;
}
// We now will perform the same operation using the same inputs on CPU,
// to compare the results for equality
std::vector<test_type> results;
results.reserve(numElements);
for (int i = 0; i < numElements; ++i)
{
results.emplace_back(boost::int128::gcd(in1[i], in2[i]));
}
// We can now compare that our operation on GPU and the same operation on CPU have identical results
for (int i = 0; i < numElements; ++i)
{
if (out[i] != results[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;
}
Expected Output
[Vector operation on 50000 elements] CUDA kernel launch with 196 blocks of 256 threads All CPU and GPU computed elements match!
SYCL Usage
Example 16. This example demonstrates how to use library types and functions inside a SYCL kernel. Define
BOOST_INT128_ENABLE_SYCL and include <sycl/sycl.hpp> before any Boost.int128 header, then compile with icpx -fsycl.// Copyright Matt Borland 2026.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <sycl/sycl.hpp>
#include <boost/int128.hpp>
#include <boost/int128/numeric.hpp>
#include <iostream>
#include <vector>
#include <random>
#include <cstdint>
#include <cstdlib>
using test_type = boost::int128::uint128_t;
// Calculates the GCD of two values on the SYCL device and verifies against the host
int main()
{
std::mt19937_64 rng {42};
const int numElements {50000};
std::cout << "[Vector operation on " << numElements << " 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 host and device
test_type* in1 {sycl::malloc_shared<test_type>(numElements, q)};
test_type* in2 {sycl::malloc_shared<test_type>(numElements, q)};
test_type* out {sycl::malloc_shared<test_type>(numElements, q)};
for (int i {0}; i < numElements; ++i)
{
in1[i] = test_type{rng(), rng()};
in2[i] = test_type{rng(), rng()};
}
// Launch the SYCL kernel: each work item computes one gcd
q.submit([&](sycl::handler& h)
{
h.parallel_for(sycl::range<1>(numElements), [=](sycl::id<1> idx)
{
const int i {static_cast<int>(idx[0])};
out[i] = boost::int128::gcd(in1[i], in2[i]);
});
}).wait();
// Perform the same operation on the host and compare
std::vector<test_type> results;
results.reserve(numElements);
for (int i {0}; i < numElements; ++i)
{
results.emplace_back(boost::int128::gcd(in1[i], in2[i]));
}
int ret {EXIT_SUCCESS};
for (int i {0}; i < numElements; ++i)
{
if (out[i] != results[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;
}
Expected Output
[Vector operation on 50000 elements] SYCL device: Intel(R) ... All CPU and GPU computed elements match!