<numeric> Support

Saturating Arithmetic

Saturating arithmetic avoids the possibility of overflow or underflow, by clamping the value to a defined range should either of these situations occur. This means that on overflow the types will return std::numeric_limits::max(), and on underflow they will return std::numeric_limits::min(). The following functions are provided for saturating arithmetic, and they do not require C++26.

#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t saturating_add(uint128_t lhs, uint128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t saturating_add(int128_t lhs, int128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr uint128_t saturating_sub(uint128_t lhs, uint128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t saturating_sub(int128_t lhs, int128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr uint128_t saturating_mul(uint128_t lhs, uint128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t saturating_mul(int128_t lhs, int128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr uint128_t saturating_div(uint128_t lhs, uint128_t rhs) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t saturating_div(int128_t lhs, int128_t rhs) noexcept;

} // namespace int128
} // namespace boost

For saturating_div the only value that can overflow is the signed INT128_MIN / -1, which saturates to INT128_MAX; every other non-zero divisor gives the same result as the plain operator/. Division by zero is a precondition violation and therefore undefined behavior (the divisor must be non-zero), exactly as for the built-in __int128 types and the plain division operator; saturating_div adds no zero-divisor check.

Saturating Cast

This function converts a uint128_t or int128_t to a TargetType, saturating rather than wrapping when the value is out of the target’s range. TargetType is constrained (via SFINAE) to the library’s set of reduced integer types: the standard signed and unsigned integer types (excluding bool and plain char), int128_t, uint128_t, and the compiler’s native 128-bit integer types where available. Should the TargetType not be able to represent the value it is set to either std::numeric_limits<TargetType>::max() or std::numeric_limits<TargetType>::min() depending on whether the situation is overflow or underflow.

#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

template <typename TargetType>
BOOST_INT128_HOST_DEVICE constexpr TargetType saturating_cast(uint128_t value) noexcept;

template <typename TargetType>
BOOST_INT128_HOST_DEVICE constexpr TargetType saturating_cast(int128_t value) noexcept;

} // namespace int128
} // namespace boost

Greatest Common Divisor (GCD)

Computes the greatest common divisor of the magnitudes of a and b.

#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t gcd(uint128_t a, uint128_t b) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t gcd(int128_t a, int128_t b) noexcept;

} // namespace int128
} // namespace boost

Returns 0 when both a and b are 0; otherwise returns the greatest common divisor of abs(a) and abs(b), which is non-negative. In particular, gcd(a, 0) returns abs(a) and gcd(0, b) returns abs(b). This matches the results of std::gcd for every operand whose magnitude is representable in the result type. Both overloads are noexcept and usable in a constant expression.

std::gcd leaves the behavior undefined when abs(a) or abs(b) is not representable in the result type; this library defines that case instead. The only such operand is BOOST_INT128_INT128_MIN, whose magnitude 2^127 is one greater than BOOST_INT128_INT128_MAX. The magnitude is still computed correctly, because the two’s-complement bit pattern of BOOST_INT128_INT128_MIN reinterpreted as unsigned is exactly 2^127, so the divisor returned is always arithmetically correct (for example, gcd(BOOST_INT128_INT128_MIN, 6) == 2). The single exception is when the divisor is itself 2^127, which occurs only for gcd(BOOST_INT128_INT128_MIN, 0) and gcd(BOOST_INT128_INT128_MIN, BOOST_INT128_INT128_MIN): because 2^127 is not representable as a positive int128_t, it is returned as the negative value BOOST_INT128_INT128_MIN.

Least Common Multiple (LCM)

Computes the least common multiple of the magnitudes of a and b.

#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t lcm(uint128_t a, uint128_t b) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t lcm(int128_t a, int128_t b) noexcept;

} // namespace int128
} // namespace boost

Returns 0 when either a or b is 0; otherwise returns the least common multiple of abs(a) and abs(b). This matches the results of std::lcm for every operand whose magnitude, and whose least common multiple, is representable in the result type. Both overloads are noexcept and usable in a constant expression.

std::lcm leaves the behavior undefined when abs(a) or abs(b) is not representable in the result type, or when the least common multiple itself is not representable; this library defines those cases instead of trapping or throwing. The result is the true least common multiple of the two magnitudes reduced modulo 2^128, consistent with the two’s-complement wrap-around of the arithmetic operators. For the signed overload that reduced value is then interpreted as a two’s-complement int128_t and may therefore be negative; a BOOST_INT128_INT128_MIN operand contributes the magnitude 2^127, computed correctly as described for gcd above.

Midpoint

Computes the midpoint of a and b, rounding towards a.

#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t midpoint(uint128_t a, uint128_t b) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t midpoint(int128_t a, int128_t b) noexcept;

} // namespace int128
} // namespace boost