Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 38 additions & 21 deletions doc/modules/ROOT/pages/numeric.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,53 @@ https://www.boost.org/LICENSE_1_0.txt
== 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 result will be the maximum value of the type, and on underflow it will be the minimum value of the type.
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.

[source, c++]
----

#include <boost/int128/numeric.hpp>
----

[source, c++]
----
namespace boost {
namespace int128 {

constexpr uint128_t add_sat(const uint128_t& lhs, const uint128_t& rhs) noexcept;
constexpr int128_t add_sat(const int128_t& lhs, const int128_t& rhs) noexcept;
constexpr uint128_t add_sat(uint128_t lhs, uint128_t rhs) noexcept;

constexpr int128_t add_sat(int128_t lhs, int128_t rhs) noexcept;

constexpr uint128_t sub_sat(uint128_t lhs, uint128_t rhs) noexcept;

constexpr uint128_t sub_sat(const uint128_t& lhs, const uint128_t& rhs) noexcept;
constexpr int128_t sub_sat(const int128_t& lhs, const int128_t& rhs) noexcept;
constexpr int128_t sub_sat(int128_t lhs, int128_t rhs) noexcept;

constexpr uint128_t mul_sat(const uint128_t& lhs, const uint128_t& rhs) noexcept;
constexpr int128_t mul_sat(const int128_t& lhs, const int128_t& rhs) noexcept;
constexpr uint128_t mul_sat(uint128_t lhs, uint128_t rhs) noexcept;

constexpr uint128_t div_sat(const uint128_t& lhs, const uint128_t& rhs) noexcept;
constexpr int128_t div_sat(const int128_t& lhs, const int128_t& rhs) noexcept;
constexpr int128_t mul_sat(int128_t lhs, int128_t rhs) noexcept;

constexpr uint128_t div_sat(uint128_t lhs, uint128_t rhs) noexcept;

constexpr int128_t div_sat(int128_t lhs, int128_t rhs) noexcept;

} // namespace int128
} // namespace boost

----

[#saturating_cast]
== Saturating Cast

This function allows a `LibraryIntegerType` (i.e. `uint128_t` or `int128_t`) to be safely casted to another integer type to include built-in and hardware integer types (`TargetIntegerType`).
Should the `TargetIntegerType` not be able to represent the value of the `LibraryIntegerType`, it will be clamped to either the maximum or minimum value of `TargetIntegerType` depending on whether the situation is overflow or underflow.
Should the `TargetIntegerType` not be able to represent the value of the `LibraryIntegerType` it will be set to either `std::numeric_limits::max()` or `std::numeric_limits::min()` depending on if the situation is overflow or underflow.

[source, c++]
----
#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

template <typename TargetIntegerType, typename LibraryIntegerType>
constexpr TargetIntegerType saturate_cast(const LibraryIntegerType& x) noexcept;
constexpr <typename LibraryIntegerType, typename TargetIntegerType>
constexpr TargetIntegerType saturate_cast(LibraryIntegerType x) noexcept;

} // namespace int128
} // namespace boost
Expand All @@ -66,14 +71,18 @@ Computes the greatest common divisor of `a` and `b`.

[source, c++]
----
#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

constexpr uint128_t gcd(const uint128_t& a, const uint128_t& b) noexcept;
constexpr int128_t gcd(const int128_t& a, const int128_t& b) noexcept;
constexpr uint128_t gcd(uint128_t a, uint128_t b) noexcept;

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

} // namespace int128
} // namespace boost

----

[#lcm]
Expand All @@ -83,14 +92,18 @@ Computes the least common multiple of `a` and `b`.

[source, c++]
----
#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

constexpr uint128_t lcm(const uint128_t& a, const uint128_t& b) noexcept;
constexpr int128_t lcm(const int128_t& a, const int128_t& b) noexcept;
constexpr uint128_t lcm(uint128_t a, uint128_t b) noexcept;

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

} // namespace int128
} // namespace boost

----

[#midpoint]
Expand All @@ -100,12 +113,16 @@ Computes the midpoint of `a` and `b`, rounding towards `a`.

[source, c++]
----
#include <boost/int128/numeric.hpp>

namespace boost {
namespace int128 {

constexpr uint128_t midpoint(const uint128_t& a, const uint128_t& b) noexcept;
constexpr int128_t midpoint(const int128_t& a, const int128_t& b) noexcept;
constexpr uint128_t midpoint(uint128_t a, uint128_t b) noexcept;

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

} // namespace int128
} // namespace boost

----
6 changes: 1 addition & 5 deletions include/boost/int128/cstdlib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ constexpr i128div_t div(const int128_t x, const int128_t y) noexcept
const auto unsigned_res {div(abs_lhs, abs_rhs)};

const auto negative_quot {(x.high < 0) != (y.high < 0)};
#if defined(_MSC_VER) && !defined(__GNUC__)
const auto negative_rem {static_cast<bool>(x.high < 0)};
#else
const auto negative_rem {static_cast<bool>((x.high < 0) != (y.high < 0))};
#endif
const auto negative_rem {x.high < 0};

i128div_t res {static_cast<int128_t>(unsigned_res.quot), static_cast<int128_t>(unsigned_res.rem)};

Expand Down
4 changes: 0 additions & 4 deletions include/boost/int128/detail/int128_imp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3100,11 +3100,7 @@ constexpr int128_t operator%(const int128_t lhs, const int128_t rhs) noexcept
}
#else

#if defined(_MSC_VER) && !defined(__GNUC__)
const auto is_neg{static_cast<bool>(lhs < 0)};
#else
const auto is_neg {static_cast<bool>((lhs < 0) != (rhs < 0))};
#endif

int128_t remainder {};

Expand Down
1 change: 1 addition & 0 deletions include/boost/int128/fmt_format.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wconversion"
# pragma GCC diagnostic ignored "-Wsign-conversion"
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif

#include <fmt/base.h>
Expand Down
63 changes: 28 additions & 35 deletions include/boost/int128/numeric.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ BOOST_INT128_INLINE_CONSTEXPR bool is_reduced_integer_v {reduced_integers<Intege

} // namespace detail

BOOST_INT128_EXPORT constexpr uint128_t add_sat(const uint128_t& x, const uint128_t& y) noexcept
BOOST_INT128_EXPORT constexpr uint128_t add_sat(const uint128_t x, const uint128_t y) noexcept
{
const auto z {x + y};

Expand All @@ -64,7 +64,7 @@ BOOST_INT128_EXPORT constexpr uint128_t add_sat(const uint128_t& x, const uint12
return z;
}

BOOST_INT128_EXPORT constexpr uint128_t sub_sat(const uint128_t& x, const uint128_t& y) noexcept
BOOST_INT128_EXPORT constexpr uint128_t sub_sat(const uint128_t x, const uint128_t y) noexcept
{
const auto z {x - y};

Expand All @@ -76,16 +76,16 @@ BOOST_INT128_EXPORT constexpr uint128_t sub_sat(const uint128_t& x, const uint12
return z;
}

BOOST_INT128_EXPORT constexpr int128_t add_sat(const int128_t& x, const int128_t& y) noexcept;
BOOST_INT128_EXPORT constexpr int128_t sub_sat(const int128_t& x, const int128_t& y) noexcept;
BOOST_INT128_EXPORT constexpr int128_t add_sat(int128_t x, int128_t y) noexcept;
BOOST_INT128_EXPORT constexpr int128_t sub_sat(int128_t x, int128_t y) noexcept;

#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4307) // Addition Overflow
# pragma warning(disable : 4146) // Unary minus applied to unsigned type
#endif

constexpr int128_t add_sat(const int128_t& x, const int128_t& y) noexcept
constexpr int128_t add_sat(const int128_t x, const int128_t y) noexcept
{
if (x >= 0 && y >= 0)
{
Expand Down Expand Up @@ -113,7 +113,7 @@ constexpr int128_t add_sat(const int128_t& x, const int128_t& y) noexcept
}
}

constexpr int128_t sub_sat(const int128_t& x, const int128_t& y) noexcept
constexpr int128_t sub_sat(const int128_t x, const int128_t y) noexcept
{
if (x <= 0 && y >= 0)
{
Expand Down Expand Up @@ -141,7 +141,7 @@ constexpr int128_t sub_sat(const int128_t& x, const int128_t& y) noexcept
# pragma warning(pop)
#endif

BOOST_INT128_EXPORT constexpr uint128_t mul_sat(const uint128_t& x, const uint128_t& y) noexcept
BOOST_INT128_EXPORT constexpr uint128_t mul_sat(const uint128_t x, const uint128_t y) noexcept
{
const auto x_bits {bit_width(x)};
const auto y_bits {bit_width(y)};
Expand Down Expand Up @@ -175,12 +175,12 @@ BOOST_INT128_EXPORT constexpr int128_t mul_sat(const int128_t& x, const int128_t
return res;
}

BOOST_INT128_EXPORT constexpr uint128_t div_sat(const uint128_t& x, const uint128_t& y) noexcept
BOOST_INT128_EXPORT constexpr uint128_t div_sat(const uint128_t x, const uint128_t y) noexcept
{
return x / y;
}

BOOST_INT128_EXPORT constexpr int128_t div_sat(const int128_t& x, const int128_t& y) noexcept
BOOST_INT128_EXPORT constexpr int128_t div_sat(const int128_t x, const int128_t y) noexcept
{
if (BOOST_INT128_UNLIKELY(x == (std::numeric_limits<int128_t>::min)() && y == -1))
{
Expand All @@ -197,7 +197,7 @@ BOOST_INT128_EXPORT constexpr int128_t div_sat(const int128_t& x, const int128_t
#endif

BOOST_INT128_EXPORT template <typename TargetType, std::enable_if_t<detail::is_reduced_integer_v<TargetType>, bool> = true>
constexpr TargetType saturate_cast(const uint128_t& value) noexcept
constexpr TargetType saturate_cast(const uint128_t value) noexcept
{
BOOST_INT128_IF_CONSTEXPR (std::is_same<uint128_t, TargetType>::value)
{
Expand All @@ -219,7 +219,7 @@ constexpr TargetType saturate_cast(const uint128_t& value) noexcept
#endif

BOOST_INT128_EXPORT template <typename TargetType, std::enable_if_t<detail::is_reduced_integer_v<TargetType>, bool> = true>
constexpr TargetType saturate_cast(const int128_t& value) noexcept
constexpr TargetType saturate_cast(const int128_t value) noexcept
{
BOOST_INT128_IF_CONSTEXPR (std::is_same<int128_t, TargetType>::value)
{
Expand Down Expand Up @@ -283,22 +283,18 @@ constexpr std::uint64_t gcd64(std::uint64_t x, std::uint64_t y) noexcept

} // namespace detail

constexpr uint128_t gcd(const uint128_t& a_in, const uint128_t& b_in) noexcept
constexpr uint128_t gcd(uint128_t a, uint128_t b) noexcept
{
// Base case
if (a_in == 0U)
if (a == 0U)
{
return b_in;
return b;
}
if (b_in == 0U)
if (b == 0U)
{
return a_in;
return a;
}

// Local copies since we modify these
auto a {a_in};
auto b {b_in};

const auto a_zero {countr_zero(a)};
const auto b_zero {countr_zero(b)};
const auto shift {b_zero < a_zero ? b_zero : a_zero};
Expand Down Expand Up @@ -330,7 +326,7 @@ constexpr uint128_t gcd(const uint128_t& a_in, const uint128_t& b_in) noexcept
return a << shift; // LCOV_EXCL_LINE : Should be unreachable, but this is also the correct answer
}

constexpr int128_t gcd(const int128_t& a, const int128_t& b) noexcept
constexpr int128_t gcd(const int128_t a, const int128_t b) noexcept
{
return static_cast<int128_t>(gcd(static_cast<uint128_t>(abs(a)), static_cast<uint128_t>(abs(b))));
}
Expand All @@ -340,7 +336,7 @@ constexpr int128_t gcd(const int128_t& a, const int128_t& b) noexcept
// but very slow impl that we know works.
#if !(defined(_M_IX86) && !defined(_NDEBUG))

constexpr uint128_t lcm(const uint128_t& a, const uint128_t& b) noexcept
constexpr uint128_t lcm(const uint128_t a, const uint128_t b) noexcept
{
if (a == 0U || b == 0U)
{
Expand All @@ -356,19 +352,16 @@ constexpr uint128_t lcm(const uint128_t& a, const uint128_t& b) noexcept

#else

constexpr uint128_t lcm(const uint128_t& a_in, const uint128_t& b_in) noexcept
constexpr uint128_t lcm(uint128_t a, uint128_t b) noexcept
{
if (a_in == 0U || b_in == 0U)
if (a == 0U || b == 0U)
{
return 0;
}

// Local copies since we modify these
auto a {a_in};
auto b {b_in};

unsigned shift{};
while ((a & 1U) == 0U && (b & 1U) == 0U)
while ((a & 1U) == 0U && (b & 1U) == 0U)
{
a >>= 1U;
b >>= 1U;
Expand All @@ -381,24 +374,24 @@ constexpr uint128_t lcm(const uint128_t& a_in, const uint128_t& b_in) noexcept
std::swap(a, b);
}

uint128_t lcm_val{a};
uint128_t lcm{a};

while (lcm_val % b != 0U)
while (lcm % b != 0U)
{
lcm_val += a;
lcm += a;
}

return lcm_val << shift;
return lcm << shift;
}

#endif

constexpr int128_t lcm(const int128_t& a, const int128_t& b) noexcept
constexpr int128_t lcm(const int128_t a, const int128_t b) noexcept
{
return static_cast<int128_t>(lcm(static_cast<uint128_t>(abs(a)), static_cast<uint128_t>(abs(b))));
}

constexpr uint128_t midpoint(const uint128_t& a, const uint128_t& b) noexcept
constexpr uint128_t midpoint(const uint128_t a, const uint128_t b) noexcept
{
// Bit manipulation formula works for unsigned integers
auto mid {(a & b) + ((a ^ b) >> 1)};
Expand All @@ -412,7 +405,7 @@ constexpr uint128_t midpoint(const uint128_t& a, const uint128_t& b) noexcept
return mid;
}

constexpr int128_t midpoint(const int128_t& a, const int128_t& b) noexcept
constexpr int128_t midpoint(const int128_t a, const int128_t b) noexcept
{
// For signed integers, we use a + (b - a) / 2 or a - (a - b) / 2
// The subtraction is done in unsigned arithmetic to handle overflow correctly
Expand Down