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
6 changes: 4 additions & 2 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
** xref:examples.adoc#examples_bit[`<bit>` support (Bitwise Operations)]
** xref:examples.adoc#examples_numeric[`<numeric>` support (Saturating Arithmetic)]
** xref:examples.adoc#examples_numeric_algorithms[`<numeric>` support (Numeric Algorithms)]
** xref:examples.adoc#examples_integer_division[`<numeric>` support (Integer Division)]
** xref:examples.adoc#examples_checked[Checked Arithmetic]
** xref:examples.adoc#examples_mixed_sign[Mixed Signedness Arithmetic]
** xref:examples.adoc#examples_boost_math_random[Boost Math and Random Integration]
Expand Down Expand Up @@ -38,7 +39,7 @@
*** xref:api_reference.adoc#api_formatting[Formatting]
*** xref:api_reference.adoc#api_iostream[`<iostream>`]
*** xref:api_reference.adoc#api_ios[`<ios>`]
*** xref:api_reference.adoc#api_numeric[`<numeric>` (saturating ops, gcd, lcm)]
*** xref:api_reference.adoc#api_numeric[`<numeric>` (saturating ops, gcd, lcm, integer division)]
*** xref:api_reference.adoc#api_string[`<string>` (`to_string`)]
*** xref:api_reference.adoc#api_utilities[Utilities (`ipow`, `isqrt`, `powm`, etc)]
** xref:api_reference.adoc#api_macros[Macros]
Expand Down Expand Up @@ -71,7 +72,8 @@
* xref:charconv.adoc[`<charconv>` (`from_chars` and `to_chars`)]
* xref:stream.adoc[`<iostream>` support]
* xref:hash.adoc[`std::hash` support]
* xref:numeric.adoc[`<numeric>` (saturating ops, gcd, lcm)]
* xref:numeric.adoc[`<numeric>` (saturating ops, gcd, lcm, integer division)]
** xref:numeric.adoc#int_div[Integer Division]
* xref:string.adoc[`<string>` (`to_string`)]
* xref:format.adoc[]
** xref:format.adoc#fmt_format[pass:[{fmt}]]
Expand Down
41 changes: 40 additions & 1 deletion doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ https://www.boost.org/LICENSE_1_0.txt

| xref:cstdlib.adoc#div_structs[`i128div_t`]
| Result type for `div(int128_t, int128_t)`

| xref:numeric.adoc#div_result[`div_result<T>`]
| Result type for the `div_rem_*` functions
|===

[#api_functions]
Expand Down Expand Up @@ -272,6 +275,42 @@ Listed by analogous STL header.

| xref:numeric.adoc#midpoint[`midpoint`]
| Midpoint between two values

| xref:numeric.adoc#int_div[`div_to_zero`, `div_rem_to_zero`]
| Division rounding towards zero

| xref:numeric.adoc#int_div[`div_away_zero`, `div_rem_away_zero`]
| Division rounding away from zero

| xref:numeric.adoc#int_div[`div_to_pos_inf`, `div_rem_to_pos_inf`]
| Division rounding towards positive infinity

| xref:numeric.adoc#int_div[`div_to_neg_inf`, `div_rem_to_neg_inf`]
| Division rounding towards negative infinity

| xref:numeric.adoc#int_div[`div_euclid`, `div_rem_euclid`]
| Euclidean division

| xref:numeric.adoc#int_div[`div_ties_to_zero`, `div_rem_ties_to_zero`]
| Division rounding to nearest, ties towards zero

| xref:numeric.adoc#int_div[`div_ties_away_zero`, `div_rem_ties_away_zero`]
| Division rounding to nearest, ties away from zero

| xref:numeric.adoc#int_div[`div_ties_to_pos_inf`, `div_rem_ties_to_pos_inf`]
| Division rounding to nearest, ties towards positive infinity

| xref:numeric.adoc#int_div[`div_ties_to_neg_inf`, `div_rem_ties_to_neg_inf`]
| Division rounding to nearest, ties towards negative infinity

| xref:numeric.adoc#int_div[`div_ties_to_odd`, `div_rem_ties_to_odd`]
| Division rounding to nearest, ties to the odd quotient

| xref:numeric.adoc#int_div[`div_ties_to_even`, `div_rem_ties_to_even`]
| Division rounding to nearest, ties to the even quotient

| xref:numeric.adoc#int_div[`rem_euclid`]
| Euclidean remainder, always in `[0, abs(y))`
|===

[#api_string]
Expand Down Expand Up @@ -464,7 +503,7 @@ Listed by analogous STL header.
| User-defined literals for `int128_t` and `uint128_t`

| xref:numeric.adoc[`<boost/int128/numeric.hpp>`]
| Numeric algorithms (gcd, lcm, midpoint)
| Numeric algorithms (gcd, lcm, midpoint, integer division)

| xref:string.adoc[`<boost/int128/string.hpp>`]
| `to_string` overloads
Expand Down
57 changes: 57 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,63 @@ midpoint(-100, -50) = -75
----
====

[#examples_integer_division]
== Integer Division (<numeric>)

The xref:numeric.adoc#int_div[`div_*` family] divides with every rounding mode other than the truncation `operator/` performs.
Two sections repay a close look: `rem_euclid` is the only remainder that is never negative, which is what makes it usable for wrapping an offset into a range, and `div_to_pos_inf` computes a ceiling without the `(length + block - 1) / block` idiom that wraps near the top of the range.

.This https://github.com/cppalliance/int128/blob/develop/examples/integer_division.cpp[example] demonstrates the rounding modes, tie breaking, and the combined quotient and remainder functions
====
[source, c++]
----
include::example$integer_division.cpp[]
----

.Expected Output
[listing]
----
=== Every rounding mode on -12 / 5 ===
div_to_zero = -2
div_away_zero = -3
div_to_pos_inf = -2
div_to_neg_inf = -3
div_euclid = -3
div_ties_to_zero = -2

=== Tie breaking on -7 / 2 ===
div_ties_to_zero = -3
div_ties_away_zero = -4
div_ties_to_pos_inf = -3
div_ties_to_neg_inf = -4
div_ties_to_odd = -3
div_ties_to_even = -4

=== Quotient and remainder from one division ===
div_rem_to_neg_inf(-12, 5): quotient = -3, remainder = 3
div_rem_to_zero(-12, 5): quotient = -2, remainder = -2
quotient * y + remainder = -12

=== Euclidean remainder is never negative ===
-9 % 7 = -2, rem_euclid(-9, 7) = 5
-8 % 7 = -1, rem_euclid(-8, 7) = 6
-7 % 7 = 0, rem_euclid(-7, 7) = 0
-6 % 7 = -6, rem_euclid(-6, 7) = 1

=== Ceiling division without overflow ===
length = 340282366920938463463374607431768211455
div_to_pos_inf(length, 1000) = 340282366920938463463374607431768212
div_to_zero(length, 1000) = 340282366920938463463374607431768211
(length + 999) / 1000 = 0 (wrapped)

=== Unbiased rounding of a scaled value ===
1500 / 1000: ties_to_even = 2, ties_away_zero = 2
2500 / 1000: ties_to_even = 2, ties_away_zero = 3
3500 / 1000: ties_to_even = 4, ties_away_zero = 4
-1500 / 1000: ties_to_even = -2, ties_away_zero = -2
----
====

[#examples_checked]
== Checked Arithmetic

Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/file_structure.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The entire library can be consumed via `<boost/int128.hpp>`, or by independently
| User-defined literals (`_u128`, `_i128`)

| xref:numeric.adoc[`<boost/int128/numeric.hpp>`]
| Numeric functions (`gcd`, `lcm`, saturating arithmetic)
| Numeric functions (`gcd`, `lcm`, saturating arithmetic, integer division)

| xref:random.adoc[`<boost/int128/random.hpp>`]
| Traits for usage with Boost.Random
Expand Down
158 changes: 158 additions & 0 deletions doc/modules/ROOT/pages/numeric.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,161 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t midpoint(int128_t a, int128_t b) noe
} // namespace boost

----

[#int_div]
== Integer Division

`operator/` only ever rounds towards zero.
These functions divide with each of the other rounding modes, and are the 128-bit counterparts of the `std::div_*` family proposed for the standard library by wg21.link/p3724[P3724 (Integer division)].
They *do not* require C++26, and every one of them is `constexpr` and available on device.

[#div_result]
=== `div_result`

Both halves of a division, mirroring `std::div_result<T>` from the paper.
It is an aggregate, so `div_result<int128_t>{q, r}` and structured bindings both work.
Equality is always available; the ordering operators are available whenever the compiler supports `operator<=>`, and compare the quotient before the remainder.

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

namespace boost {
namespace int128 {

template <typename T>
struct div_result
{
T quotient;
T remainder;
};

template <typename T>
BOOST_INT128_HOST_DEVICE constexpr bool operator==(const div_result<T>& lhs, const div_result<T>& rhs) noexcept;

template <typename T>
BOOST_INT128_HOST_DEVICE constexpr bool operator!=(const div_result<T>& lhs, const div_result<T>& rhs) noexcept;

// Only when BOOST_INT128_HAS_SPACESHIP_OPERATOR is defined
template <typename T>
BOOST_INT128_HOST_DEVICE constexpr std::strong_ordering operator<=>(const div_result<T>& lhs, const div_result<T>& rhs) noexcept;

} // namespace int128
} // namespace boost
----

[#div_rounding_modes]
=== Rounding Modes

Each mode comes in three forms: `div_<mode>` returns the quotient, `div_rem_<mode>` returns the quotient and the matching remainder, and for Euclidean division `rem_euclid` returns the remainder alone.

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

namespace boost {
namespace int128 {

BOOST_INT128_HOST_DEVICE constexpr uint128_t div_<mode>(uint128_t x, uint128_t y) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t div_<mode>(int128_t x, int128_t y) noexcept;

BOOST_INT128_HOST_DEVICE constexpr div_result<uint128_t> div_rem_<mode>(uint128_t x, uint128_t y) noexcept;

BOOST_INT128_HOST_DEVICE constexpr div_result<int128_t> div_rem_<mode>(int128_t x, int128_t y) noexcept;

BOOST_INT128_HOST_DEVICE constexpr uint128_t rem_euclid(uint128_t x, uint128_t y) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t rem_euclid(int128_t x, int128_t y) noexcept;

} // namespace int128
} // namespace boost
----

`<mode>` is one of the eleven names below.
The last two columns give the quotient for a dividend that does not divide evenly, and for one that lands exactly halfway between two integers.

[cols="1,2,1,1", options="header"]
|===
| `<mode>` | Rounds | `-12 / 5` (exactly `-2.4`) | `-7 / 2` (exactly `-3.5`)

| `to_zero`
| Towards zero, which is what `operator/` does
| `-2`
| `-3`

| `away_zero`
| Away from zero
| `-3`
| `-4`

| `to_pos_inf`
| Towards positive infinity (ceiling)
| `-2`
| `-3`

| `to_neg_inf`
| Towards negative infinity (floor)
| `-3`
| `-4`

| `euclid`
| So that the remainder is non-negative
| `-3`
| `-4`

| `ties_to_zero`
| To nearest, an exact tie towards zero
| `-2`
| `-3`

| `ties_away_zero`
| To nearest, an exact tie away from zero
| `-2`
| `-4`

| `ties_to_pos_inf`
| To nearest, an exact tie towards positive infinity
| `-2`
| `-3`

| `ties_to_neg_inf`
| To nearest, an exact tie towards negative infinity
| `-2`
| `-4`

| `ties_to_odd`
| To nearest, an exact tie to the odd quotient
| `-2`
| `-3`

| `ties_to_even`
| To nearest, an exact tie to the even quotient
| `-2`
| `-4`
|===

An exact tie is only possible when the divisor is even, so the six `ties_` modes differ from each other only there; everywhere else they all return the nearest integer.

[#div_semantics]
=== Semantics

The remainder returned by `div_rem_<mode>` is the one that matches the quotient: `x == quotient * y + remainder` for the signed overloads.
For the unsigned overloads the same identity holds modulo 2^128^, because a quotient rounded up leaves a negative remainder that has to wrap; `div_rem_away_zero(uint128_t{7}, uint128_t{2})` therefore returns a quotient of `4` and a remainder of `BOOST_INT128_UINT128_MAX`, since `7 - 4 * 2` is `-1`.
The quotient itself never overflows: rounding only moves the quotient when the division is inexact, and an inexact division has a divisor whose magnitude is at least two.

The unsigned overloads exist for every mode, but a non-negative quotient collapses several of them together.
`div_to_zero`, `div_to_neg_inf`, and `div_euclid` are all plain truncation, `div_away_zero` and `div_to_pos_inf` are the same rounding up, `div_ties_to_neg_inf` matches `div_ties_to_zero`, and `div_ties_to_pos_inf` matches `div_ties_away_zero`.
The full set is still provided so that generic code can name a rounding mode without also having to know the signedness of the operands.

`rem_euclid` returns a value in `[0, abs(y))`, which makes it the function to reach for when wrapping a possibly negative offset into a range; `operator%` instead takes its sign from the dividend.
The signed overload is correct for `y == BOOST_INT128_INT128_MIN` even though `abs(y)` is not representable as a positive `int128_t`, because the correction is applied in unsigned arithmetic.
`div_euclid` agrees with `div_to_neg_inf` when the divisor is positive, and with `div_to_pos_inf` when it is negative.

Each `div_rem_<mode>` performs exactly one division, so it is the cheaper way to obtain both halves; computing `x / y` and `x % y` separately costs two divisions, since neither the compiler nor the library can share the work across the two operators.

A zero divisor is a precondition violation and therefore undefined behavior, exactly as for `operator/` and xref:numeric.adoc#sat_arith[`saturating_div`]; no zero-divisor check is performed.
The signed overloads are likewise undefined for `BOOST_INT128_INT128_MIN / -1`, whose quotient is not representable.

NOTE: The paper leaves these functions non-`noexcept` because a precondition violation is undefined behavior.
This library marks them `noexcept` for consistency with the rest of its interface, which does the same.
2 changes: 2 additions & 0 deletions doc/modules/ROOT/pages/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ The following books, papers, and specifications inform the algorithms and interf
- ISO/IEC, _Programming Languages - C++_ (the `<bit>`, `<numeric>`, `<charconv>`, and `<format>` interfaces mirrored by this library).

- ISO/IEC 9899:2024 (C23), _Programming Languages - C_ (the `<stdckdint.h>` checked-integer interface mirrored by `ckd_add`, `ckd_sub`, and `ckd_mul`).

- Jan Schultke, wg21.link/p3724[P3724, _Integer division_] (the rounding modes and the `div_result` interface mirrored by the xref:numeric.adoc#int_div[`div_*` family]).
17 changes: 17 additions & 0 deletions doc/modules/ROOT/pages/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ https://www.boost.org/LICENSE_1_0.txt

== Unreleased

=== New: integer division with every rounding mode

`<boost/int128/numeric.hpp>` now provides the `div_*` family proposed for the standard library by wg21.link/p3724[P3724 (Integer division)], for both `uint128_t` and `int128_t`.
`operator/` only rounds towards zero; these functions round away from zero, towards either infinity, and to nearest under each of the six tie-breaking rules, along with Euclidean division and its always non-negative remainder.

[source, c++]
----
div_to_neg_inf(int128_t{-7}, int128_t{2}); // -4, where -7 / 2 is -3
div_ties_to_even(int128_t{-7}, int128_t{2}); // -4, breaking the tie at -3.5
rem_euclid(int128_t{-7}, int128_t{2}); // 1, where -7 % 2 is -1

const auto res = div_rem_to_pos_inf(x, y); // res.quotient and res.remainder, one division
----

Each mode also has a `div_rem_` form returning a `div_result<T>` with both halves from a single division, and there is a standalone `rem_euclid`.
See xref:numeric.adoc#int_div[Integer Division] for the full list.

=== Breaking: `int128_t::high` is now `std::uint64_t`

`int128_t` previously stored `std::uint64_t low` and `std::int64_t high`.
Expand Down
Loading
Loading