Skip to content

Commit 051606b

Browse files
authored
Merge pull request #409 from cppalliance/checked
Add C23 style checked arithmetic
2 parents 3618337 + 9c0a7c9 commit 051606b

8 files changed

Lines changed: 993 additions & 1 deletion

File tree

doc/modules/ROOT/nav.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
** xref:examples.adoc#examples_bit[`<bit>` support]
88
** xref:examples.adoc#examples_numeric[`<numeric>` support (Saturating Arithmetic)]
99
** xref:examples.adoc#examples_numeric_algorithms[`<numeric>` support (Numeric Algorithms)]
10+
** xref:examples.adoc#examples_checked[Checked Arithmetic]
1011
** xref:examples.adoc#examples_mixed_sign[Mixed Signedness Arithmetic]
1112
** xref:examples.adoc#examples_to_string[String Conversion (to_string)]
1213
** xref:examples.adoc#examples_boost_math_random[Boost Math and Random Integration]
@@ -60,6 +61,9 @@
6061
* xref:string.adoc[]
6162
* xref:utilities.adoc[]
6263
** xref:utilities.adoc#powm[Modular Exponentiation]
64+
** xref:utilities.adoc#ipow[Integer Power]
65+
** xref:utilities.adoc#isqrt[Integer Square Root]
66+
** xref:utilities.adoc#checked[Checked Arithmetic]
6367
* Benchmarks
6468
** xref:u128_benchmarks.adoc[]
6569
*** xref:u128_benchmarks.adoc#u128_linux[Linux]

doc/modules/ROOT/pages/api_reference.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,21 @@ Listed by analogous STL header.
286286

287287
| xref:utilities.adoc#powm[`powm`]
288288
| Modular exponentiation `(base ^ exp) mod m`
289+
290+
| xref:utilities.adoc#ipow[`ipow`]
291+
| Integer power `base ^ exp` (wraps modulo `2^128`)
292+
293+
| xref:utilities.adoc#isqrt[`isqrt`]
294+
| Integer square root `floor(sqrt(n))`
295+
296+
| xref:utilities.adoc#checked[`ckd_add`]
297+
| Checked addition (C23 `<stdckdint.h>` contract)
298+
299+
| xref:utilities.adoc#checked[`ckd_sub`]
300+
| Checked subtraction (C23 `<stdckdint.h>` contract)
301+
302+
| xref:utilities.adoc#checked[`ckd_mul`]
303+
| Checked multiplication (C23 `<stdckdint.h>` contract)
289304
|===
290305

291306
[#api_macros]

doc/modules/ROOT/pages/examples.adoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,38 @@ midpoint(-100, -50) = -75
278278
----
279279
====
280280

281+
[#examples_checked]
282+
== Checked Arithmetic
283+
284+
.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
285+
====
286+
[source, c++]
287+
----
288+
include::example$checked_arithmetic.cpp[]
289+
----
290+
291+
.Expected Output
292+
[listing]
293+
----
294+
=== Results That Fit ===
295+
ckd_add(20, 22): overflow=false, result=42
296+
297+
=== Addition Overflow ===
298+
ckd_add(UINT128_MAX, 1): overflow=true, wrapped=0
299+
300+
=== Subtraction Underflow ===
301+
ckd_sub(0, 1): overflow=true, wrapped=340282366920938463463374607431768211455
302+
303+
=== Multiplication Overflow ===
304+
ckd_mul(INT128_MAX, 2): overflow=true, wrapped=-2
305+
ckd_mul(INT128_MIN, -1): overflow=true, wrapped=-170141183460469231731687303715884105728
306+
307+
=== Mixed Types ===
308+
ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=false, result=2
309+
ckd_mul<uint8_t>(20, 20): overflow=true, wrapped=144
310+
----
311+
====
312+
281313
[#examples_mixed_sign]
282314
== Mixed Signedness Arithmetic
283315

doc/modules/ROOT/pages/utilities.adoc

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,142 @@ Negative bases are reduced before exponentiation; `(std::numeric_limits<int128_t
6060
| `base == 0` and `exp > 0`
6161
| `0`
6262

63-
| Signed overload with `m <= 0` or `exp < 0`
63+
| Signed overload with non-positive `m` or negative `exp`
6464
| `0` (modular exponentiation requires a positive modulus; a negative exponent would require a modular inverse, which this interface does not provide)
6565
|===
66+
67+
[#ipow]
68+
== Integer Power
69+
70+
Computes `base ^ exp` by exponentiation by squaring, with a non-negative 64-bit exponent.
71+
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*`.
72+
`ipow(base, exp)` is therefore equivalent to multiplying `base` by itself `exp` times.
73+
74+
[source, c++]
75+
----
76+
namespace boost {
77+
namespace int128 {
78+
79+
BOOST_INT128_HOST_DEVICE constexpr uint128_t ipow(uint128_t base, std::uint64_t exp) noexcept;
80+
81+
BOOST_INT128_HOST_DEVICE constexpr int128_t ipow(int128_t base, std::uint64_t exp) noexcept;
82+
83+
} // namespace int128
84+
} // namespace boost
85+
----
86+
87+
The exponent is unsigned, so negative powers (which are not integers) cannot be requested.
88+
Because the result wraps on overflow rather than saturating or reporting an error, `ipow` is appropriate when rollover semantics are intended.
89+
90+
=== Special Cases
91+
92+
[cols="1,1", options="header"]
93+
|===
94+
| Input | Result
95+
96+
| `exp == 0`
97+
| `1` (including `ipow(0, 0) == 1`, following the conventional definition `0^0 == 1`)
98+
99+
| `base == 0` and `exp > 0`
100+
| `0`
101+
102+
| `base ^ exp` exceeds 128 bits
103+
| The low 128 bits of the true power, matching the rollover of `operator*`
104+
|===
105+
106+
[#isqrt]
107+
== Integer Square Root
108+
109+
Computes the integer square root `floor(sqrt(n))`: the largest integer `r` whose square does not exceed `n`.
110+
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.
111+
112+
[source, c++]
113+
----
114+
namespace boost {
115+
namespace int128 {
116+
117+
BOOST_INT128_HOST_DEVICE constexpr uint128_t isqrt(uint128_t n) noexcept;
118+
119+
BOOST_INT128_HOST_DEVICE constexpr int128_t isqrt(int128_t n) noexcept;
120+
121+
} // namespace int128
122+
} // namespace boost
123+
----
124+
125+
=== Special Cases
126+
127+
[cols="1,1", options="header"]
128+
|===
129+
| Input | Result
130+
131+
| `n < 0` (signed overload)
132+
| `0` (a real square root does not exist)
133+
134+
| `n >= 0`
135+
| `floor(sqrt(n))`, the largest `r` whose square does not exceed `n` (so `isqrt(0) == 0` and `isqrt(1) == 1`)
136+
|===
137+
138+
[#checked]
139+
== Checked Arithmetic
140+
141+
`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.
142+
143+
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`.
144+
The function returns `false` when `*result` correctly represents the mathematical result of the operation.
145+
Otherwise it returns `true`, and `*result` is set to the mathematical result wrapped around (reduced modulo `2^N`) to the width `N` of `*result`.
146+
`*result` is always written, whether or not the operation overflowed.
147+
148+
[source, c++]
149+
----
150+
namespace boost {
151+
namespace int128 {
152+
153+
template <typename T1, typename T2, typename T3>
154+
BOOST_INT128_HOST_DEVICE constexpr bool ckd_add(T1* result, T2 a, T3 b) noexcept;
155+
156+
template <typename T1, typename T2, typename T3>
157+
BOOST_INT128_HOST_DEVICE constexpr bool ckd_sub(T1* result, T2 a, T3 b) noexcept;
158+
159+
template <typename T1, typename T2, typename T3>
160+
BOOST_INT128_HOST_DEVICE constexpr bool ckd_mul(T1* result, T2 a, T3 b) noexcept;
161+
162+
} // namespace int128
163+
} // namespace boost
164+
----
165+
166+
The three type parameters are independent: the result type and the two operand types may differ in width and signedness.
167+
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.
168+
169+
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.
170+
In addition to the standard and extended integer types, the library's `uint128_t` and `int128_t` are accepted.
171+
172+
The following example exercises all three operations, including the wrap-around, the `INT128_MIN * -1` case, and the mixed-type behavior described above.
173+
174+
.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
175+
====
176+
[source, c++]
177+
----
178+
include::example$checked_arithmetic.cpp[]
179+
----
180+
181+
.Expected Output
182+
[listing]
183+
----
184+
=== Results That Fit ===
185+
ckd_add(20, 22): overflow=false, result=42
186+
187+
=== Addition Overflow ===
188+
ckd_add(UINT128_MAX, 1): overflow=true, wrapped=0
189+
190+
=== Subtraction Underflow ===
191+
ckd_sub(0, 1): overflow=true, wrapped=340282366920938463463374607431768211455
192+
193+
=== Multiplication Overflow ===
194+
ckd_mul(INT128_MAX, 2): overflow=true, wrapped=-2
195+
ckd_mul(INT128_MIN, -1): overflow=true, wrapped=-170141183460469231731687303715884105728
196+
197+
=== Mixed Types ===
198+
ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=false, result=2
199+
ckd_mul<uint8_t>(20, 20): overflow=true, wrapped=144
200+
----
201+
====

examples/checked_arithmetic.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2026 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
// Individual headers
6+
7+
#include <boost/int128/utilities.hpp>
8+
#include <boost/int128/iostream.hpp>
9+
10+
// Or you can do a single header
11+
12+
// #include <boost/int128.hpp>
13+
14+
#include <cstdint>
15+
#include <limits>
16+
#include <iostream>
17+
18+
int main()
19+
{
20+
using boost::int128::uint128_t;
21+
using boost::int128::int128_t;
22+
using boost::int128::ckd_add;
23+
using boost::int128::ckd_sub;
24+
using boost::int128::ckd_mul;
25+
26+
std::cout << std::boolalpha;
27+
28+
// ckd_add, ckd_sub, and ckd_mul implement the C23 stdckdint.h contract: the
29+
// operation is evaluated as if both operands had infinite range, the result
30+
// is written to *result wrapped to that type's width, and the function
31+
// returns true when the exact result did not fit.
32+
constexpr auto u_max {std::numeric_limits<uint128_t>::max()};
33+
constexpr auto i_max {std::numeric_limits<int128_t>::max()};
34+
constexpr auto i_min {std::numeric_limits<int128_t>::min()};
35+
36+
// A result that fits returns false and holds the exact value.
37+
std::cout << "=== Results That Fit ===" << std::endl;
38+
int128_t r {};
39+
bool overflow {ckd_add(&r, int128_t{20}, int128_t{22})};
40+
std::cout << "ckd_add(20, 22): overflow=" << overflow << ", result=" << r << std::endl;
41+
42+
// Addition that exceeds the type wraps modulo 2^128 and reports overflow.
43+
std::cout << "\n=== Addition Overflow ===" << std::endl;
44+
uint128_t u {};
45+
overflow = ckd_add(&u, u_max, uint128_t{1});
46+
std::cout << "ckd_add(UINT128_MAX, 1): overflow=" << overflow << ", wrapped=" << u << std::endl;
47+
48+
// Subtracting below zero in an unsigned type wraps to the top of the range.
49+
std::cout << "\n=== Subtraction Underflow ===" << std::endl;
50+
overflow = ckd_sub(&u, uint128_t{0}, uint128_t{1});
51+
std::cout << "ckd_sub(0, 1): overflow=" << overflow << ", wrapped=" << u << std::endl;
52+
53+
// Multiplication detects overflow that operator* would silently roll over,
54+
// including INT128_MIN * -1, whose true result is not representable.
55+
std::cout << "\n=== Multiplication Overflow ===" << std::endl;
56+
overflow = ckd_mul(&r, i_max, int128_t{2});
57+
std::cout << "ckd_mul(INT128_MAX, 2): overflow=" << overflow << ", wrapped=" << r << std::endl;
58+
overflow = ckd_mul(&r, i_min, int128_t{-1});
59+
std::cout << "ckd_mul(INT128_MIN, -1): overflow=" << overflow << ", wrapped=" << r << std::endl;
60+
61+
// The result type and the two operand types are independent: they may differ
62+
// in width and signedness, and the exact mathematical value is always used.
63+
std::cout << "\n=== Mixed Types ===" << std::endl;
64+
std::int64_t narrow {};
65+
overflow = ckd_add(&narrow, uint128_t{5}, int128_t{-3});
66+
std::cout << "ckd_add<int64_t>(uint128_t{5}, int128_t{-3}): overflow=" << overflow
67+
<< ", result=" << narrow << std::endl;
68+
69+
// Narrow targets make the wrap-around easy to see (400 modulo 256 is 144).
70+
std::uint8_t byte {};
71+
overflow = ckd_mul(&byte, std::uint8_t{20}, std::uint8_t{20});
72+
std::cout << "ckd_mul<uint8_t>(20, 20): overflow=" << overflow
73+
<< ", wrapped=" << static_cast<int>(byte) << std::endl;
74+
75+
return 0;
76+
}

0 commit comments

Comments
 (0)