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:
-
int128anduint128(cross-type), -
int128/uint128and any built-in integer type (signed or unsigned, including the compiler’s 128-bit__int128/unsigned __int128where 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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
-
__float128is not supported, even on the platforms where the compiler provides it and allows__int128 op __float128. Onlyfloat,double, andlong doubleparticipate. -
long doubleis unavailable on the CUDA and SYCL device, so thelong doubleoverloads are not declared whenBOOST_INT128_HAS_GPU_SUPPORTis defined, matching the conversion operators. -
On a platform whose
long doubleis IBM double-double, currently ppc64le,long doubleresults 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.floatanddoubleare unaffected, and so is every otherlong doubleformat.