Mixed Type Operations

Conversions

Conversion between the two types is implicit: each type provides a non-explicit converting constructor from the other, so an int128 converts to a uint128 (and vice versa) wherever the target type is expected, as documented in the above class descriptions.

Operator Overloads Across Types

All comparison, arithmetic, bitwise, and shift operators are provided across:

  • int128 and uint128 (cross-type),

  • int128 / uint128 and any built-in integer type (signed or unsigned, including the compiler’s 128-bit __int128 / unsigned __int128 where supported).

The compound assignment operators are provided in both operand orders, so a built-in integer accepts a 128-bit right operand as well; see Compound Assignment onto a Built-in Integer below.

Additionally, the operators that the built-in 128-bit integers allow with a floating-point operand are provided between int128 / uint128 and float, double, and long double; see Operations With Floating-Point Types below.

The behavior and return type of every mixed-sign overload follow the C++ usual arithmetic conversions, identical to what the equivalent built-in __int128 / unsigned __int128 operation would produce, including two’s-complement wrap-around semantics.

Result Type Rules

Operands Common type Result of arithmetic / bitwise

int128 and uint128 (same rank, mixed sign)

uint128

uint128

int128 and unsigned __int128 (same rank, mixed sign)

uint128

uint128

uint128 and __int128 (same rank, mixed sign)

uint128

uint128

int128 and a small unsigned built-in (uint8_t to uint64_t)

int128

int128

uint128 and a small signed built-in (int8_t to int64_t)

uint128

uint128

int128 or uint128 and float

float

float

int128 or uint128 and double

double

double

int128 or uint128 and long double

long double

long double

For shift operators (<<, >>), the result type follows the LHS type after integral promotion, regardless of the RHS, matching the built-in shift rules. int128 and uint128 are not promoted, so a 128-bit LHS yields itself. A built-in LHS of lesser rank than int promotes, and does so whether or not it is signed, so bool, unsigned char, and unsigned short all yield a signed int. A shift count that is negative or greater than or equal to the width of the shifted operand, which is 128 when the LHS is int128 or uint128, is undefined behavior, exactly as for the built-in shift operators; see the shift operator reference for the precise rules.

For comparison operators (==, !=, <, <=, >, >=), the return type is always bool and the comparison is performed on the operands after they have been converted to the common type above.

Cross-type Operator Signatures

namespace boost {
namespace int128 {

//=====================================
// Comparison Operators
//=====================================

BOOST_INT128_HOST_DEVICE constexpr bool operator==(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator==(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator!=(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator!=(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator<(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator<(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator<=(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator<=(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator>(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator>(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator>=(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr bool operator>=(int128 lhs, uint128 rhs);

//=====================================
// Arithmetic Operators
//=====================================

BOOST_INT128_HOST_DEVICE constexpr uint128 operator+(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator+(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator-(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator-(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator*(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator*(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator/(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator/(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator%(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator%(int128 lhs, uint128 rhs);

//=====================================
// Bitwise Operators
//=====================================

BOOST_INT128_HOST_DEVICE constexpr uint128 operator|(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator|(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator&(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator&(int128 lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator^(uint128 lhs, int128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator^(int128 lhs, uint128 rhs);

//=====================================
// Shift Operators
//=====================================
// Result type follows the LHS.

BOOST_INT128_HOST_DEVICE constexpr int128  operator<<(int128  lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator<<(uint128 lhs, int128  rhs);
BOOST_INT128_HOST_DEVICE constexpr int128  operator>>(int128  lhs, uint128 rhs);
BOOST_INT128_HOST_DEVICE constexpr uint128 operator>>(uint128 lhs, int128  rhs);

} // namespace int128
} // namespace boost

The cross-type arithmetic and bitwise operators return the same value as static_cast<uint128>(lhs) op static_cast<uint128>(rhs). The comparison operators return the same value as that expression compared with the matching unsigned operator.

Operations with built-in __int128 / unsigned __int128

When the compiler provides 128-bit built-in integer types, all of the operators above are also available between a library type and the built-in type of opposite signedness (e.g. uint128 op __int128, unsigned __int128 op int128). The result type follows the same rules as the table above (uint128 for arithmetic and bitwise; the LHS type for shifts; bool for comparisons), and the produced value is identical to what an all-built-in computation would yield.

Compound Assignment onto a Built-in Integer

Every compound assignment operator is available with a built-in integer on the left and int128 or uint128 on the right:

using boost::int128::uint128;

unsigned flags {0};
flags |= uint128{1};      // 1, and flags is still unsigned

int counter {12};
counter += boost::int128::int128{-5};   // 7

i op= v is equivalent to i = static_cast<decltype(i)>(i op v) using the binary operator from the table above, which is what the built-in 128-bit integers do. The whole operation is performed in the common type of the two operands and only the result is converted back, so a right operand wider than the left operand is never truncated first:

std::uint32_t word {0};
word += (uint128{1} << 64U) + uint128{7};   // 7, the low word of 2^64 + 7

std::uint64_t big {8};
big /= uint128{1} << 64U;                   // 0, because 8 / 2^64 is zero

The left operand keeps its own type, so a result outside its range wraps exactly as the built-in wraps. The shift operators are the exception the language makes for all integer types: they take the value and the result type from the left operand alone, and only the count from the right.

std::uint64_t bits {1};
bits <<= uint128{40};     // 2^40, a 64-bit shift by a count of 40

Available Types and Operators

Integer is any built-in integer type, signed or unsigned, from bool through long long, including the compiler’s __int128 and unsigned __int128 where supported. Every signature below exists for both uint128 and int128 on the right.

namespace boost {
namespace int128 {

template <BOOST_INT128_INTEGER_CONCEPT Integer>
BOOST_INT128_HOST_DEVICE constexpr Integer& operator|=(Integer& lhs, uint128 rhs);
// and likewise for &=, ^=, +=, -=, *=, /=, %=, <<=, >>=

} // namespace int128
} // namespace boost

A division or remainder by zero, and a shift count that is negative or at least the width of the promoted left operand, are undefined, exactly as for the built-in operators.

Operations With Floating-Point Types

int128 and uint128 support exactly the operators that the built-in 128-bit integers support with a floating-point operand, in either operand order, for float, double, and long double.

The rule is the C++ usual arithmetic conversions: the 128-bit operand is converted to the floating-point type first, and the operation is then performed entirely in that floating-point type. The result type is the floating-point type, never a 128-bit integer.

boost::int128::uint128 u {5};

auto a = u + 1.0;      // double, 6.0
auto b = 1.0F - u;     // float, -4.0F
auto c = u * 0.5;      // double, 2.5   (the operand is NOT truncated to 0 first)
bool d = u < 5.5;      // true

Because the conversion happens before the operation, and because it is rounded once, to nearest with ties to even, every result is bit-for-bit identical to the corresponding built-in __int128 / unsigned __int128 expression. That holds on every platform: where the compiler provides a built-in 128-bit integer the conversion is the compiler’s own, and where it does not the portable path rounds identically.

Available Operators

namespace boost {
namespace int128 {

// Float is float, double, or long double.
// Every signature below exists for both uint128 and int128.

//=====================================
// Arithmetic Operators
//=====================================

template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr Float operator+(uint128 lhs, Float rhs);
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr Float operator+(Float lhs, uint128 rhs);
// and likewise for -, *, /

//=====================================
// Comparison Operators
//=====================================

template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr bool operator==(uint128 lhs, Float rhs);
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr bool operator==(Float lhs, uint128 rhs);
// and likewise for !=, <, <=, >, >=

// Requires C++20. A mixed integer and floating-point comparison is a partial
// ordering, because a NaN operand is unordered with respect to every value
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr std::partial_ordering operator<=>(uint128 lhs, Float rhs);
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr std::partial_ordering operator<=>(Float lhs, uint128 rhs);

//=====================================
// Compound Assignment
//=====================================

template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr uint128& uint128::operator+=(Float rhs);
// and likewise for -=, *=, /=

template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr Float& operator+=(Float& lhs, uint128 rhs);
// and likewise for -=, *=, /=

} // namespace int128
} // namespace boost

Compound Assignment

u op= f where u is a 128-bit type and f is floating-point is equivalent to u = static_cast<uint128>(static_cast<Float>(u) op f), matching the built-in. The whole operation is performed in floating point and only the final result is converted back, truncating toward zero:

boost::int128::uint128 u {4};
u *= 2.5;              // 10, not 8: the operand is not truncated first

Where the built-in would be undefined, that is when the floating-point result is negative for a uint128, is NaN, or is outside the range of the target type, the library saturates instead, exactly as the floating-point constructor does.

f op= u where f is floating-point keeps the type of f and is equivalent to f op= static_cast<Float>(u).

Operators Not Provided

The built-in 128-bit integers do not accept a floating-point operand for the modulo, bitwise, or shift operators, so neither do int128 and uint128. The following are declared as deleted, which makes them a compile error rather than silently converting the floating-point operand:

  • %, &, |, ^, <<, >> in either operand order,

  • %=, &=, |=, ^=, <<=, >>= with a floating-point right operand.

Precision of Comparisons

A comparison converts the 128-bit operand to the floating-point type before comparing, so it is only as precise as that type. This is the behavior of the built-in and of every other integer type in the language, but it is worth stating explicitly for a 128-bit integer, whose value can need far more bits than a double significand holds:

using boost::int128::uint128;

const uint128 v {(uint128{1} << 64U) + uint128{1}};  // 2^64 + 1

v == 18446744073709551616.0;   // true: v rounds to 2^64 as a double

For a comparison that is exact for every value, use the integer comparison functions cmp_equal, cmp_less, and friends with an integer operand instead.

Limitations

  • __float128 is not supported, even on the platforms where the compiler provides it and allows __int128 op __float128. Only float, double, and long double participate.

  • long double is unavailable on the CUDA and SYCL device, so the long double overloads are not declared when BOOST_INT128_HAS_GPU_SUPPORT is defined, matching the conversion operators.

  • On a platform whose long double is IBM double-double, currently ppc64le, long double results are reproducible but not bitwise reproducible: that format is not canonical and its arithmetic is not correctly rounded, so two expressions computing the same product can differ in the last bits. float and double are unaffected, and so is every other long double format.