Utilities

The <boost/int128/utilities.hpp> header collects helpers that operate on the library types directly and would not fit naturally into the analogous STL-style headers. The functions are tuned specifically for uint128_t and int128_t rather than being template generalizations, which allows the library to dispatch to a fast path based on the shape of the modulus.

#include <boost/int128/utilities.hpp>

Modular Exponentiation

Computes (base ^ exp) mod m. The naive expression pow(base, exp) % m is unusable for 128-bit inputs because base ^ exp overflows almost immediately; powm performs the reduction inside the exponentiation loop and selects an algorithm based on the modulus:

  • If has_single_bit(m) is true, modular reduction collapses to a bitmask and no division is performed.

  • If the modulus fits in 64 bits (m.high == 0), the loop runs on 64-bit lanes. Each squaring is a single 64x64 → 128 multiply followed by a 128-by-64 reduction.

  • Otherwise the modulus uses the full 128 bits, and powm uses a shift-and-add inner multiply so that no intermediate value ever exceeds 128 bits. This avoids forming the 256-bit product that a naive square-and-multiply implementation would require.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t powm(uint128_t base, uint128_t exp, uint128_t m) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t powm(int128_t base, int128_t exp, int128_t m) noexcept;

} // namespace int128
} // namespace boost

The signed overload returns the non-negative residue in the range [0, m), matching the convention used by pow(a, b, m) in Python and most arbitrary-precision libraries. Negative bases are reduced before exponentiation; (std::numeric_limits<int128_t>::min)() is handled correctly even though its magnitude is not representable in int128_t.

Special Cases

Input Result

m == 0

0 (consistent with the library’s convention for division by zero)

m == 1

0

exp == 0

1 (including powm(0, 0, m), which follows the conventional definition 0^0 == 1)

base == 0 and exp > 0

0

Signed overload with non-positive m or negative exp

0 (modular exponentiation requires a positive modulus; a negative exponent would require a modular inverse, which this interface does not provide)

Integer Power

Computes base ^ exp by exponentiation by squaring, with a non-negative 64-bit exponent. Unlike powm there is no modulus: the result is the true power reduced modulo 2^128, which is the same rollover behavior as the library’s operator*. ipow(base, exp) is therefore equivalent to multiplying base by itself exp times.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t ipow(uint128_t base, std::uint64_t exp) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t ipow(int128_t base, std::uint64_t exp) noexcept;

} // namespace int128
} // namespace boost

The exponent is unsigned, so negative powers (which are not integers) cannot be requested. Because the result wraps on overflow rather than saturating or reporting an error, ipow is appropriate when rollover semantics are intended.

Special Cases

Input Result

exp == 0

1 (including ipow(0, 0) == 1, following the conventional definition 0^0 == 1)

base == 0 and exp > 0

0

base ^ exp exceeds 128 bits

The low 128 bits of the true power, matching the rollover of operator*

Integer Square Root

Computes the integer square root floor(sqrt(n)): the largest integer r whose square does not exceed n. The computation runs entirely in integer arithmetic using Newton’s method, so it is exact (no floating-point rounding) and usable in a constexpr context.

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t isqrt(uint128_t n) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t isqrt(int128_t n) noexcept;

} // namespace int128
} // namespace boost

Special Cases

Input Result

n < 0 (signed overload)

0 (a real square root does not exist)

n >= 0

floor(sqrt(n)), the largest r whose square does not exceed n (so isqrt(0) == 0 and isqrt(1) == 1)

Checked Arithmetic

ckd_add, ckd_sub, and ckd_mul implement the checked integer arithmetic interface introduced by C23’s <stdckdint.h>, but without requiring a C23 toolchain; they are available in C++14 and later.

Each function computes a + b, a - b, or a * b respectively, as if both operands were represented in a signed integer type with infinite range, and then converts that mathematical result to the type pointed to by result. The function returns false when *result correctly represents the mathematical result of the operation. Otherwise it returns true, and *result is set to the mathematical result wrapped around (reduced modulo 2^N) to the width N of *result. *result is always written, whether or not the operation overflowed.

namespace boost {
namespace int128 {

template <typename T1, typename T2, typename T3>
BOOST_INT128_HOST_DEVICE constexpr bool ckd_add(T1* result, T2 a, T3 b) noexcept;

template <typename T1, typename T2, typename T3>
BOOST_INT128_HOST_DEVICE constexpr bool ckd_sub(T1* result, T2 a, T3 b) noexcept;

template <typename T1, typename T2, typename T3>
BOOST_INT128_HOST_DEVICE constexpr bool ckd_mul(T1* result, T2 a, T3 b) noexcept;

} // namespace int128
} // namespace boost

The three type parameters are independent: the result type and the two operand types may differ in width and signedness. The operation always uses the exact mathematical value of each operand, so a negative signed value added to an unsigned value, or a product that needs up to 256 bits internally, is evaluated correctly.

Following the C23 rules, T1, T2, and T3 may be any integer type other than bool, plain char, an enumerated type, or a bit-precise (_BitInt) type. In addition to the standard and extended integer types, the library’s uint128_t and int128_t are accepted.

The following example exercises all three operations, including the wrap-around, the INT128_MIN * -1 case, and the mixed-type behavior described above.

Example 1. 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