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
1 change: 0 additions & 1 deletion doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
** xref:examples.adoc#examples_saturating[Saturating Arithmetic]
** xref:examples.adoc#examples_overflowing[Overflowing Arithmetic]
** xref:examples.adoc#examples_checked[Checked Arithmetic]
** xref:examples.adoc#examples_wrapping[Wrapping Arithmetic]
** xref:examples.adoc#examples_strict[Strict Arithmetic]
** xref:examples.adoc#examples_generic[Generic Policy-Parameterized Arithmetic]
** xref:examples.adoc#examples_literals[Literals]
Expand Down
3 changes: 0 additions & 3 deletions doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,6 @@ https://www.boost.org/LICENSE_1_0.txt
| xref:policies.adoc[`checked_add`, `checked_sub`, `checked_mul`, `checked_div`, `checked_mod`]
| Checked arithmetic (return `std::nullopt` on overflow)

| xref:policies.adoc[`wrapping_add`, `wrapping_sub`, `wrapping_mul`, `wrapping_div`, `wrapping_mod`]
| Wrapping arithmetic (wrap silently)

| xref:policies.adoc[`strict_add`, `strict_sub`, `strict_mul`, `strict_div`, `strict_mod`]
| Strict arithmetic (call `std::exit(EXIT_FAILURE)` on error)

Expand Down
3 changes: 1 addition & 2 deletions doc/modules/ROOT/pages/comparisons.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ This page summarizes the key differences and provides a runnable example demonst
| Implicit construction from built-in types is allowed, including from `bool`.

| *Overflow policies*
| Named free functions per-operation: `wrapping_add`, `saturating_sub`, `checked_mul`, `overflowing_div`, etc. The default operator always throws.
| Named free functions per-operation: `saturating_add`, `saturating_sub`, `checked_mul`, `overflowing_div`, etc. The default operator always throws.
| Policy is selected as a template parameter on the type itself (`safe<T, PromotionPolicy, ExceptionPolicy>`). No per-operation policy functions.

| *Mixed-width arithmetic*
Expand Down Expand Up @@ -93,7 +93,6 @@ safe_numbers threw: Underflow detected in unsigned subtraction
safe_numerics: -1

--- Alternative policies (SafeNumbers) ---
wrapping_add(250, 10) = 4
saturating_add(250, 10) = 255
checked_add(250, 10) = nullopt
overflowing_add(250, 10) = {4, true}
Expand Down
8 changes: 3 additions & 5 deletions doc/modules/ROOT/pages/design.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ auto b = u8{255} + u8{1}; // throws
Different domains require different responses to overflow:

- Signal processing often needs *saturation*: clamp to the representable range and continue.
- Cryptographic algorithms require *wrapping*: modular arithmetic is the correct behavior.
- Safety-critical systems may require *hard termination*: no exceptions, no recovery, just stop.
- General application code benefits from *exceptions*: the error is reported and can be handled.
- Some code needs to *detect and branch*: check whether overflow occurred without changing control flow.
Expand All @@ -97,12 +96,11 @@ For other needs, named free functions make the policy explicit at each call site
using boost::safe_numbers::u8;

auto a = saturating_add(u8{200}, u8{100}); // a == u8{255}
auto b = wrapping_add(u8{200}, u8{100}); // b == u8{44}
auto c = checked_add(u8{200}, u8{100}); // c == std::nullopt
auto [d, overflow] = overflowing_add(u8{200}, u8{100}); // d == u8{44}, overflow == true
auto b = checked_add(u8{200}, u8{100}); // b == std::nullopt
auto [c, overflow] = overflowing_add(u8{200}, u8{100}); // c == u8{44}, overflow == true
----

This approach is inspired by Rust's primitive type API, where `checked_add`, `saturating_add`, and `wrapping_add` are methods on integer types.
This approach is inspired by Rust's primitive type API, where `checked_add` and `saturating_add` are methods on integer types.

For generic code that needs to be parameterized on the overflow policy, the library provides policy-parameterized free functions:

Expand Down
32 changes: 0 additions & 32 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,6 @@ Safe: 1000000000 * 5 = 5000000000
----
====

[#examples_wrapping]
== Wrapping Arithmetic

Wrapping arithmetic performs standard C unsigned integer wrapping behavior - results wrap around modulo 2^N.
This matches the behavior of built-in unsigned integers and is useful for implementing counters, checksums, or hash functions.

.This https://github.com/boostorg/safe_numbers/blob/develop/examples/wrapping_arithmetic.cpp[example] demonstrates wrapping arithmetic operations.
====
[source, c++]
----
include::example$wrapping_arithmetic.cpp[]
----

Output:
----
wrapping_add(255, 2) = 1
wrapping_sub(0, 1) = 255
wrapping_mul(200, 2) = 144
wrapping_add(UINT32_MAX, 1) = 0
wrapping_sub(0, 1) = 4294967295
wrapping_add(100, 50) = 150
wrapping_sub(100, 50) = 50
wrapping_mul(100, 50) = 5000
Counter sequence: 254 255 0 1 2
----
====

[#examples_strict]
== Strict Arithmetic

Expand Down Expand Up @@ -193,13 +166,11 @@ Output:
----
add<throw_exception>(100, 50) = 150
add<saturate>(100, 50) = 150
add<wrapping>(100, 50) = 150
add<strict>(100, 50) = 150
add<overflow_tuple>(100, 50) = 150 (overflowed: false)
add<checked>(100, 50) = 150
add<checked>(max, 1) = nullopt (overflow)
add<saturate>(max, 1) = 4294967295
add<wrapping>(max, 1) = 0
----
====

Expand Down Expand Up @@ -405,9 +376,6 @@ checked_shl(u8(1), 4) = 16
checked_shl(u8(255), 1) = nullopt (overflow)
checked_shr(u8(1), 8) = nullopt (overflow)

wrapping_shl(u8(255), 1) = 254
wrapping_shr(u8(1), 8) = 0

shl<saturate>(u32(1), 30) = 1073741824
shl<saturate>(u32(max), 1) = 4294967295
shr<checked>(u32(8), 1) = 4
Expand Down
55 changes: 0 additions & 55 deletions doc/modules/ROOT/pages/policies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ enum class overflow_policy
saturate, // Clamp to the representable range
overflow_tuple, // Wrap and return a flag indicating overflow
checked, // Return std::nullopt on overflow/underflow
wrapping, // Wrap silently
strict, // Call std::exit(EXIT_FAILURE) on error
widen, // Promote to the next wider type (add/mul only)
};
Expand Down Expand Up @@ -60,11 +59,6 @@ enum class overflow_policy
| Returns `std::nullopt`
| Yes

| `wrapping`
| Wraps silently
| Throws `std::domain_error`
| Add/Sub/Mul: Yes, Div/Mod: No

| `strict`
| Calls `std::exit(EXIT_FAILURE)`
| Calls `std::exit(EXIT_FAILURE)`
Expand Down Expand Up @@ -165,34 +159,6 @@ These functions return `std::nullopt` on overflow, underflow, or division by zer
- `checked_div`: Returns the quotient, or `std::nullopt` on division by zero
- `checked_mod`: Returns the remainder, or `std::nullopt` on division by zero

=== Wrapping Arithmetic

[source,c++]
----
template <UnsignedLibType T>
constexpr T wrapping_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr T wrapping_mod(T lhs, T rhs);
----

These functions wrap on overflow without any indication:

- `wrapping_add`: Returns the wrapped sum
- `wrapping_sub`: Returns the wrapped difference
- `wrapping_mul`: Returns the wrapped product
- `wrapping_div`: Returns the quotient; throws `std::domain_error` on division by zero
- `wrapping_mod`: Returns the remainder; throws `std::domain_error` on division by zero

=== Strict Arithmetic

[source,c++]
Expand Down Expand Up @@ -297,20 +263,6 @@ constexpr std::optional<T> checked_shr(T lhs, T rhs) noexcept;
- `checked_shl`: Returns the shifted value, or `std::nullopt` on overflow
- `checked_shr`: Returns the shifted value, or `std::nullopt` when the shift amount is >= the type width

=== Wrapping Shifts

[source,c++]
----
template <UnsignedLibType T>
constexpr T wrapping_shl(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_shr(T lhs, T rhs) noexcept;
----

- `wrapping_shl`: Performs the shift, allowing bits to be lost without indication
- `wrapping_shr`: Returns `0` when the shift amount is >= the type width; otherwise performs the shift normally

=== Strict Shifts

[source,c++]
Expand Down Expand Up @@ -372,9 +324,6 @@ The return type depends on the policy:
| `overflow_policy::checked`
| `std::optional<T>`

| `overflow_policy::wrapping`
| `T`

| `overflow_policy::strict`
| `T`

Expand Down Expand Up @@ -449,8 +398,4 @@ The default operators and some named functions throw exceptions on error:
| `overflowing_div`, `overflowing_mod`
| `std::domain_error`
| Division by zero

| `wrapping_div`, `wrapping_mod`
| `std::domain_error`
| Division by zero
|===
33 changes: 1 addition & 32 deletions doc/modules/ROOT/pages/unsigned_integers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -193,22 +193,6 @@ constexpr std::optional<T> checked_div(T lhs, T rhs) noexcept;
template <UnsignedLibType T>
constexpr std::optional<T> checked_mod(T lhs, T rhs) noexcept;

// Wrapping arithmetic (wrap without indication)
template <UnsignedLibType T>
constexpr T wrapping_add(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_sub(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_mul(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_div(T lhs, T rhs);

template <UnsignedLibType T>
constexpr T wrapping_mod(T lhs, T rhs);

// Strict arithmetic (call std::exit(EXIT_FAILURE) on error)
template <UnsignedLibType T>
constexpr T strict_add(T lhs, T rhs) noexcept;
Expand Down Expand Up @@ -246,13 +230,6 @@ constexpr std::optional<T> checked_shl(T lhs, T rhs) noexcept;
template <UnsignedLibType T>
constexpr std::optional<T> checked_shr(T lhs, T rhs) noexcept;

// Wrapping shifts (wrap without indication)
template <UnsignedLibType T>
constexpr T wrapping_shl(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr T wrapping_shr(T lhs, T rhs) noexcept;

// Strict shifts (call std::exit(EXIT_FAILURE) on error)
template <UnsignedLibType T>
constexpr T strict_shl(T lhs, T rhs) noexcept;
Expand Down Expand Up @@ -456,10 +433,6 @@ constexpr std::pair<T, bool> overflowing_shr(T lhs, T rhs) noexcept;
constexpr std::optional<T> checked_shl(T lhs, T rhs) noexcept;
constexpr std::optional<T> checked_shr(T lhs, T rhs) noexcept;

// Wrapping: perform the shift ignoring overflow
constexpr T wrapping_shl(T lhs, T rhs) noexcept;
constexpr T wrapping_shr(T lhs, T rhs) noexcept;

// Strict: call std::exit(EXIT_FAILURE) on overflow
constexpr T strict_shl(T lhs, T rhs) noexcept;
constexpr T strict_shr(T lhs, T rhs) noexcept;
Expand Down Expand Up @@ -489,10 +462,6 @@ The behavior of each policy on shift overflow:
| Returns `std::nullopt`
| Returns `std::nullopt`

| `wrapping`
| Performs the shift (bits shifted out are lost)
| Returns `0`

| `strict`
| Calls `std::exit(EXIT_FAILURE)`
| Calls `std::exit(EXIT_FAILURE)`
Expand Down Expand Up @@ -539,7 +508,7 @@ constexpr auto operator-() const noexcept; // compile-time error
- `+`: Returns a copy of the value (identity). This is consistent with built-in unsigned integer behavior.
- `-`: Deliberately disabled. Any use of unary minus on an unsigned safe integer is a compile-time error via `static_assert`.
While pass:[C++] defines unary minus on unsigned integers as modular negation (`2^N - x`), this is a common source of bugs and is prohibited by this library.
Use a wrapping subtraction policy from zero or the maximum value if modular negation is needed.
Use an overflowing subtraction from zero or `std::numeric_limits::max()` if modular negation is needed.

=== Mixed-Width Operations

Expand Down
3 changes: 0 additions & 3 deletions doc/proposal/proposal.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ class u32
// Saturates to std::numeric_limits<u32>::max()
friend constexpr u32 saturating_add(u32 lhs, u32 rhs) noexcept;

// Allows well defined wrapping behavior
friend constexpr u32 wrapping_add(u32 lhs, u32 rhs) noexcept;

...
};
----
Expand Down
8 changes: 0 additions & 8 deletions examples/bitwise_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,6 @@ int main()

std::cout << '\n';

// Wrapping shifts: perform the shift ignoring overflow
std::cout << "wrapping_shl(u8(255), 1) = "
<< static_cast<unsigned>(wrapping_shl(u8{0xFF}, u8{1})) << '\n';
std::cout << "wrapping_shr(u8(1), 8) = "
<< static_cast<unsigned>(wrapping_shr(u8{1}, u8{8})) << '\n';

std::cout << '\n';

// Generic policy-parameterized shifts
std::cout << "shl<saturate>(u32(1), 30) = "
<< shl<overflow_policy::saturate>(u32{1}, u32{30}) << '\n';
Expand Down
6 changes: 0 additions & 6 deletions examples/generic_arithmetic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ int main()
{
const auto throwing {add<overflow_policy::throw_exception>(a, b)};
const auto saturated {add<overflow_policy::saturate>(a, b)};
const auto wrapped {add<overflow_policy::wrapping>(a, b)};
const auto strict {add<overflow_policy::strict>(a, b)};

std::cout << "add<throw_exception>(100, 50) = " << throwing << std::endl;
std::cout << "add<saturate>(100, 50) = " << saturated << std::endl;
std::cout << "add<wrapping>(100, 50) = " << wrapped << std::endl;
std::cout << "add<strict>(100, 50) = " << strict << std::endl;
}

Expand Down Expand Up @@ -69,10 +67,6 @@ int main()
// saturate policy clamps to max
const auto sat_result {add<overflow_policy::saturate>(max_val, one)};
std::cout << "add<saturate>(max, 1) = " << sat_result << std::endl;

// wrapping policy wraps around
const auto wrap_result {add<overflow_policy::wrapping>(max_val, one)};
std::cout << "add<wrapping>(max, 1) = " << wrap_result << std::endl;
}

return 0;
Expand Down
7 changes: 1 addition & 6 deletions examples/safe_numerics_comparison.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// 2. SafeNumbers requires explicit construction and forbids implicit
// conversions; SafeNumerics allows implicit construction from built-ins.
// 3. SafeNumbers provides named free functions for alternative overflow
// policies (wrapping_add, saturating_sub, checked_mul, etc.);
// policies (saturating_add, saturating_sub, checked_mul, etc.);
// SafeNumerics selects behavior via template policy parameters on the type.
// 4. SafeNumbers forbids mixed-width arithmetic at compile time;
// SafeNumerics promotes operands using C++ native promotion rules.
Expand Down Expand Up @@ -155,11 +155,6 @@ int main()
const auto x = safe_num::u8{250U};
const auto y = safe_num::u8{10U};

// Wrapping: modular arithmetic like built-in unsigned
const auto wrapped = safe_num::wrapping_add(x, y);
std::cout << "wrapping_add(250, 10) = " << wrapped << std::endl;
// Output: 4

// Saturating: clamp at the max/min boundary
const auto saturated = safe_num::saturating_add(x, y);
std::cout << "saturating_add(250, 10) = " << saturated << std::endl;
Expand Down
Loading