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
4 changes: 4 additions & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
** xref:examples.adoc#examples_bit[`<bit>` support]
** xref:examples.adoc#examples_numeric[`<numeric>` support (Saturating Arithmetic)]
** xref:examples.adoc#examples_numeric_algorithms[`<numeric>` support (Numeric Algorithms)]
** xref:examples.adoc#examples_checked[Checked Arithmetic]
** xref:examples.adoc#examples_mixed_sign[Mixed Signedness Arithmetic]
** xref:examples.adoc#examples_to_string[String Conversion (to_string)]
** xref:examples.adoc#examples_boost_math_random[Boost Math and Random Integration]
Expand Down Expand Up @@ -60,6 +61,9 @@
* xref:string.adoc[]
* xref:utilities.adoc[]
** xref:utilities.adoc#powm[Modular Exponentiation]
** xref:utilities.adoc#ipow[Integer Power]
** xref:utilities.adoc#isqrt[Integer Square Root]
** xref:utilities.adoc#checked[Checked Arithmetic]
* Benchmarks
** xref:u128_benchmarks.adoc[]
*** xref:u128_benchmarks.adoc#u128_linux[Linux]
Expand Down
15 changes: 15 additions & 0 deletions doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,21 @@ Listed by analogous STL header.

| xref:utilities.adoc#powm[`powm`]
| Modular exponentiation `(base ^ exp) mod m`

| xref:utilities.adoc#ipow[`ipow`]
| Integer power `base ^ exp` (wraps modulo `2^128`)

| xref:utilities.adoc#isqrt[`isqrt`]
| Integer square root `floor(sqrt(n))`

| xref:utilities.adoc#checked[`ckd_add`]
| Checked addition (C23 `<stdckdint.h>` contract)

| xref:utilities.adoc#checked[`ckd_sub`]
| Checked subtraction (C23 `<stdckdint.h>` contract)

| xref:utilities.adoc#checked[`ckd_mul`]
| Checked multiplication (C23 `<stdckdint.h>` contract)
|===

[#api_macros]
Expand Down
32 changes: 32 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,38 @@ midpoint(-100, -50) = -75
----
====

[#examples_checked]
== Checked Arithmetic

.This https://github.com/cppalliance/int128/blob/develop/examples/checked_arithmetic.cpp[example] demonstrates checked addition, subtraction, and multiplication following the C23 checked-integer contract
====
[source, c++]
----
include::example$checked_arithmetic.cpp[]
----

.Expected Output
[listing]
----
=== Results That Fit ===
ckd_add(20, 22): overflow=false, result=42

=== Addition Overflow ===
ckd_add(UINT128_MAX, 1): overflow=true, wrapped=0

=== Subtraction Underflow ===
ckd_sub(0, 1): overflow=true, wrapped=340282366920938463463374607431768211455

=== Multiplication Overflow ===
ckd_mul(INT128_MAX, 2): overflow=true, wrapped=-2
ckd_mul(INT128_MIN, -1): overflow=true, wrapped=-170141183460469231731687303715884105728

=== Mixed Types ===
ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=false, result=2
ckd_mul<uint8_t>(20, 20): overflow=true, wrapped=144
----
====

[#examples_mixed_sign]
== Mixed Signedness Arithmetic

Expand Down
138 changes: 137 additions & 1 deletion doc/modules/ROOT/pages/utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,142 @@ Negative bases are reduced before exponentiation; `(std::numeric_limits<int128_t
| `base == 0` and `exp > 0`
| `0`

| Signed overload with `m <= 0` or `exp < 0`
| Signed overload with non-positive `m` or negative `exp`
| `0` (modular exponentiation requires a positive modulus; a negative exponent would require a modular inverse, which this interface does not provide)
|===

[#ipow]
== Integer Power

Computes `base ^ exp` by exponentiation by squaring, with a non-negative 64-bit exponent.
Unlike `powm` there is no modulus: the result is the true power reduced modulo `2^128`, which is the same rollover behavior as the library's `operator*`.
`ipow(base, exp)` is therefore equivalent to multiplying `base` by itself `exp` times.

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

BOOST_INT128_HOST_DEVICE constexpr uint128_t ipow(uint128_t base, std::uint64_t exp) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t ipow(int128_t base, std::uint64_t exp) noexcept;

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

The exponent is unsigned, so negative powers (which are not integers) cannot be requested.
Because the result wraps on overflow rather than saturating or reporting an error, `ipow` is appropriate when rollover semantics are intended.

=== Special Cases

[cols="1,1", options="header"]
|===
| Input | Result

| `exp == 0`
| `1` (including `ipow(0, 0) == 1`, following the conventional definition `0^0 == 1`)

| `base == 0` and `exp > 0`
| `0`

| `base ^ exp` exceeds 128 bits
| The low 128 bits of the true power, matching the rollover of `operator*`
|===

[#isqrt]
== Integer Square Root

Computes the integer square root `floor(sqrt(n))`: the largest integer `r` whose square does not exceed `n`.
The computation runs entirely in integer arithmetic using Newton's method, so it is exact (no floating-point rounding) and usable in a `constexpr` context.

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

BOOST_INT128_HOST_DEVICE constexpr uint128_t isqrt(uint128_t n) noexcept;

BOOST_INT128_HOST_DEVICE constexpr int128_t isqrt(int128_t n) noexcept;

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

=== Special Cases

[cols="1,1", options="header"]
|===
| Input | Result

| `n < 0` (signed overload)
| `0` (a real square root does not exist)

| `n >= 0`
| `floor(sqrt(n))`, the largest `r` whose square does not exceed `n` (so `isqrt(0) == 0` and `isqrt(1) == 1`)
|===

[#checked]
== Checked Arithmetic

`ckd_add`, `ckd_sub`, and `ckd_mul` implement the checked integer arithmetic interface introduced by C23's `<stdckdint.h>`, but without requiring a C23 toolchain; they are available in C++14 and later.

Each function computes `a + b`, `a - b`, or `a * b` respectively, as if both operands were represented in a signed integer type with infinite range, and then converts that mathematical result to the type pointed to by `result`.
The function returns `false` when `*result` correctly represents the mathematical result of the operation.
Otherwise it returns `true`, and `*result` is set to the mathematical result wrapped around (reduced modulo `2^N`) to the width `N` of `*result`.
`*result` is always written, whether or not the operation overflowed.

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

template <typename T1, typename T2, typename T3>
BOOST_INT128_HOST_DEVICE constexpr bool ckd_add(T1* result, T2 a, T3 b) noexcept;

template <typename T1, typename T2, typename T3>
BOOST_INT128_HOST_DEVICE constexpr bool ckd_sub(T1* result, T2 a, T3 b) noexcept;

template <typename T1, typename T2, typename T3>
BOOST_INT128_HOST_DEVICE constexpr bool ckd_mul(T1* result, T2 a, T3 b) noexcept;

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

The three type parameters are independent: the result type and the two operand types may differ in width and signedness.
The operation always uses the exact mathematical value of each operand, so a negative signed value added to an unsigned value, or a product that needs up to 256 bits internally, is evaluated correctly.

Following the C23 rules, `T1`, `T2`, and `T3` may be any integer type other than `bool`, plain `char`, an enumerated type, or a bit-precise (`_BitInt`) type.
In addition to the standard and extended integer types, the library's `uint128_t` and `int128_t` are accepted.

The following example exercises all three operations, including the wrap-around, the `INT128_MIN * -1` case, and the mixed-type behavior described above.

.This https://github.com/cppalliance/int128/blob/develop/examples/checked_arithmetic.cpp[example] demonstrates checked addition, subtraction, and multiplication following the C23 checked-integer contract
====
[source, c++]
----
include::example$checked_arithmetic.cpp[]
----

.Expected Output
[listing]
----
=== Results That Fit ===
ckd_add(20, 22): overflow=false, result=42

=== Addition Overflow ===
ckd_add(UINT128_MAX, 1): overflow=true, wrapped=0

=== Subtraction Underflow ===
ckd_sub(0, 1): overflow=true, wrapped=340282366920938463463374607431768211455

=== Multiplication Overflow ===
ckd_mul(INT128_MAX, 2): overflow=true, wrapped=-2
ckd_mul(INT128_MIN, -1): overflow=true, wrapped=-170141183460469231731687303715884105728

=== Mixed Types ===
ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=false, result=2
ckd_mul<uint8_t>(20, 20): overflow=true, wrapped=144
----
====
76 changes: 76 additions & 0 deletions examples/checked_arithmetic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// Individual headers

#include <boost/int128/utilities.hpp>
#include <boost/int128/iostream.hpp>

// Or you can do a single header

// #include <boost/int128.hpp>

#include <cstdint>
#include <limits>
#include <iostream>

int main()
{
using boost::int128::uint128_t;
using boost::int128::int128_t;
using boost::int128::ckd_add;
using boost::int128::ckd_sub;
using boost::int128::ckd_mul;

std::cout << std::boolalpha;

// ckd_add, ckd_sub, and ckd_mul implement the C23 stdckdint.h contract: the
// operation is evaluated as if both operands had infinite range, the result
// is written to *result wrapped to that type's width, and the function
// returns true when the exact result did not fit.
constexpr auto u_max {std::numeric_limits<uint128_t>::max()};
constexpr auto i_max {std::numeric_limits<int128_t>::max()};
constexpr auto i_min {std::numeric_limits<int128_t>::min()};

// A result that fits returns false and holds the exact value.
std::cout << "=== Results That Fit ===" << std::endl;
int128_t r {};
bool overflow {ckd_add(&r, int128_t{20}, int128_t{22})};
std::cout << "ckd_add(20, 22): overflow=" << overflow << ", result=" << r << std::endl;

// Addition that exceeds the type wraps modulo 2^128 and reports overflow.
std::cout << "\n=== Addition Overflow ===" << std::endl;
uint128_t u {};
overflow = ckd_add(&u, u_max, uint128_t{1});
std::cout << "ckd_add(UINT128_MAX, 1): overflow=" << overflow << ", wrapped=" << u << std::endl;

// Subtracting below zero in an unsigned type wraps to the top of the range.
std::cout << "\n=== Subtraction Underflow ===" << std::endl;
overflow = ckd_sub(&u, uint128_t{0}, uint128_t{1});
std::cout << "ckd_sub(0, 1): overflow=" << overflow << ", wrapped=" << u << std::endl;

// Multiplication detects overflow that operator* would silently roll over,
// including INT128_MIN * -1, whose true result is not representable.
std::cout << "\n=== Multiplication Overflow ===" << std::endl;
overflow = ckd_mul(&r, i_max, int128_t{2});
std::cout << "ckd_mul(INT128_MAX, 2): overflow=" << overflow << ", wrapped=" << r << std::endl;
overflow = ckd_mul(&r, i_min, int128_t{-1});
std::cout << "ckd_mul(INT128_MIN, -1): overflow=" << overflow << ", wrapped=" << r << std::endl;

// The result type and the two operand types are independent: they may differ
// in width and signedness, and the exact mathematical value is always used.
std::cout << "\n=== Mixed Types ===" << std::endl;
std::int64_t narrow {};
overflow = ckd_add(&narrow, uint128_t{5}, int128_t{-3});
std::cout << "ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=" << overflow
<< ", result=" << narrow << std::endl;

// Narrow targets make the wrap-around easy to see (400 modulo 256 is 144).
std::uint8_t byte {};
overflow = ckd_mul(&byte, std::uint8_t{20}, std::uint8_t{20});
std::cout << "ckd_mul<uint8_t>(20, 20): overflow=" << overflow
<< ", wrapped=" << static_cast<int>(byte) << std::endl;

return 0;
}
Loading
Loading