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
19 changes: 19 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,25 @@ y >>= 4 -> 16

Left shift error: Left shift past the end of the type width
Right shift error: Right shift past the end of the type width

saturating_shl(u8(1), 4) = 16
saturating_shl(u8(255), 1) = 255
saturating_shr(u8(128), 4) = 8
saturating_shr(u8(1), 8) = 0

overflowing_shl(u8(255), 1) = 254 (overflow: true)
overflowing_shr(u8(1), 8) = 0 (overflow: true)

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
101 changes: 99 additions & 2 deletions doc/modules/ROOT/pages/policies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ https://www.boost.org/LICENSE_1_0.txt

== Description

The library provides multiple overflow handling policies for arithmetic operations.
The default arithmetic operators (`+`, `-`, `*`, `/`, `%`) use the `throw_exception` policy, but alternative free functions and a generic policy-parameterized interface allow selecting different behavior at the call site.
The library provides multiple overflow handling policies for arithmetic and shift operations.
The default arithmetic operators (`+`, `-`, `*`, `/`, `%`) and shift operators (`<<`, `>>`) use the `throw_exception` policy, but alternative free functions and a generic policy-parameterized interface allow selecting different behavior at the call site.

== The `overflow_policy` Enum

Expand Down Expand Up @@ -245,6 +245,88 @@ Only addition and multiplication are provided because subtraction, division, and

Both functions are `noexcept`.

== Named Shift Functions

The same policy variants available for arithmetic operations are also available for shift operations.
The `widen` policy is not supported for shifts.

=== Overflow Conditions

- **Left shift (`<<`)**: Overflow occurs when `bit_width(lhs) + rhs >= std::numeric_limits<BasisType>::digits` (i.e., bits would be shifted past the type width).
- **Right shift (`>>`)**: Overflow occurs when `rhs >= std::numeric_limits<BasisType>::digits` (i.e., the shift amount is greater than or equal to the type width).

=== Saturating Shifts

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

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

- `saturating_shl`: Returns the shifted value, saturating at `std::numeric_limits<T>::max()` on overflow
- `saturating_shr`: Returns the shifted value, saturating at `0` when the shift amount is >= the type width

=== Overflowing Shifts

[source,c++]
----
template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_shl(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_shr(T lhs, T rhs) noexcept;
----

- `overflowing_shl`: Returns the wrapped shifted value and `true` if overflow occurred
- `overflowing_shr`: Returns `0` and `true` if the shift amount is >= the type width; otherwise the shifted value and `false`

=== Checked Shifts

[source,c++]
----
template <UnsignedLibType T>
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;
----

- `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++]
----
template <UnsignedLibType T>
constexpr T strict_shl(T lhs, T rhs) noexcept;

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

- `strict_shl`: Returns the shifted value; calls `std::exit(EXIT_FAILURE)` on overflow
- `strict_shr`: Returns the shifted value; calls `std::exit(EXIT_FAILURE)` when the shift amount is >= the type width

All shift policy functions are `noexcept`.

== Generic Policy-Parameterized Arithmetic

[source,c++]
Expand All @@ -263,9 +345,16 @@ constexpr auto div(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto mod(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shl(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shr(T lhs, T rhs);
----

These functions accept an `overflow_policy` as a template parameter and dispatch to the corresponding named function.
The `widen` policy is not supported for `shl` or `shr`.
The return type depends on the policy:

|===
Expand Down Expand Up @@ -345,6 +434,14 @@ The default operators and some named functions throw exceptions on error:
| `std::underflow_error`
| Value is zero

| `<<`, `<<=`
| `std::overflow_error`
| `bit_width(lhs) + rhs >= digits`

| `>>`, `>>=`
| `std::overflow_error`
| `rhs >= digits`

| `saturating_div`, `saturating_mod`
| `std::domain_error`
| Division by zero
Expand Down
111 changes: 108 additions & 3 deletions doc/modules/ROOT/pages/unsigned_integers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,41 @@ constexpr T strict_div(T lhs, T rhs) noexcept;
template <UnsignedLibType T>
constexpr T strict_mod(T lhs, T rhs) noexcept;

// Saturating shifts (clamp to max/0 on overflow)
template <UnsignedLibType T>
constexpr T saturating_shl(T lhs, T rhs) noexcept;

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

// Overflowing shifts (wrap and return overflow flag)
template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_shl(T lhs, T rhs) noexcept;

template <UnsignedLibType T>
constexpr std::pair<T, bool> overflowing_shr(T lhs, T rhs) noexcept;

// Checked shifts (return std::nullopt on overflow)
template <UnsignedLibType T>
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;

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

// Generic policy-parameterized arithmetic
template <overflow_policy Policy, UnsignedLibType T>
constexpr auto add(T lhs, T rhs);
Expand All @@ -241,6 +276,13 @@ constexpr auto div(T lhs, T rhs);
template <overflow_policy Policy, UnsignedLibType T>
constexpr auto mod(T lhs, T rhs);

// Generic policy-parameterized shifts
template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shl(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shr(T lhs, T rhs);

} // namespace boost::safe_numbers
----

Expand Down Expand Up @@ -395,6 +437,69 @@ The shift operators (`<<`, `>>`) perform runtime bounds checking:
- `<<`: Throws `std::overflow_error` if the left shift would move bits past the type width. Specifically, this occurs when `bit_width(lhs) + rhs >= std::numeric_limits<BasisType>::digits`.
- `>>`: Throws `std::overflow_error` if the shift amount is greater than or equal to the type width (i.e., `rhs >= std::numeric_limits<BasisType>::digits`).

=== Shift Overflow Policies

In addition to the default throwing behavior, the shift operators support the same overflow policies as arithmetic operations.
The overflow condition for left shift is when `bit_width(lhs) + rhs >= digits`, and for right shift is when `rhs >= digits`.

[source,c++]
----
// Saturating: clamp to max (left shift) or 0 (right shift) on overflow
constexpr T saturating_shl(T lhs, T rhs) noexcept;
constexpr T saturating_shr(T lhs, T rhs) noexcept;

// Overflowing: wrap and return an overflow flag
constexpr std::pair<T, bool> overflowing_shl(T lhs, T rhs) noexcept;
constexpr std::pair<T, bool> overflowing_shr(T lhs, T rhs) noexcept;

// Checked: return std::nullopt on overflow
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;

// Generic policy-parameterized shifts
template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shl(T lhs, T rhs);

template <overflow_policy Policy, UnsignedLibType T>
constexpr auto shr(T lhs, T rhs);
----

The behavior of each policy on shift overflow:

|===
| Policy | Left Shift Overflow | Right Shift Overflow

| `saturating`
| Returns `std::numeric_limits<T>::max()`
| Returns `0`

| `overflowing`
| Returns the wrapped result with `true`
| Returns `0` with `true`

| `checked`
| 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)`
|===

All shift policy functions are `noexcept`.

=== Compound Bitwise Assignment Operators

[source,c++]
Expand Down Expand Up @@ -441,10 +546,10 @@ Use a wrapping subtraction policy from zero or the maximum value if modular nega
Operations between different width safe integer types are compile-time errors.
To perform operations between different widths, explicitly convert to the same type first.

== Alternative Arithmetic Functions
== Alternative Arithmetic and Shift Functions

For cases where throwing exceptions is not desired, alternative arithmetic functions are provided with different overflow handling policies.
See xref:policies.adoc[] for full documentation of all policies, named arithmetic functions, and the generic policy-parameterized interface.
For cases where throwing exceptions is not desired, alternative arithmetic and shift functions are provided with different overflow handling policies.
See xref:policies.adoc[] for full documentation of all policies, named functions, and the generic policy-parameterized interface.

== Constexpr Support

Expand Down
76 changes: 76 additions & 0 deletions examples/bitwise_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,81 @@ int main()
std::cerr << "Right shift error: " << e.what() << '\n';
}

std::cout << '\n';

// ---- Policy-based shift operations ----

// Saturating shifts: clamp to max (left) or zero (right) on overflow
std::cout << "saturating_shl(u8(1), 4) = "
<< static_cast<unsigned>(saturating_shl(u8{1}, u8{4})) << '\n';
std::cout << "saturating_shl(u8(255), 1) = "
<< static_cast<unsigned>(saturating_shl(u8{0xFF}, u8{1})) << '\n';
std::cout << "saturating_shr(u8(128), 4) = "
<< static_cast<unsigned>(saturating_shr(u8{0x80}, u8{4})) << '\n';
std::cout << "saturating_shr(u8(1), 8) = "
<< static_cast<unsigned>(saturating_shr(u8{1}, u8{8})) << '\n';

std::cout << '\n';

// Overflowing shifts: return the result and an overflow flag
{
const auto [result, flag] = overflowing_shl(u8{0xFF}, u8{1});
std::cout << "overflowing_shl(u8(255), 1) = "
<< static_cast<unsigned>(result) << " (overflow: "
<< std::boolalpha << flag << ")\n";
}
{
const auto [result, flag] = overflowing_shr(u8{1}, u8{8});
std::cout << "overflowing_shr(u8(1), 8) = "
<< static_cast<unsigned>(result) << " (overflow: "
<< std::boolalpha << flag << ")\n";
}

std::cout << '\n';

// Checked shifts: return std::optional (nullopt on overflow)
{
const auto result = checked_shl(u8{1}, u8{4});
if (result)
{
std::cout << "checked_shl(u8(1), 4) = "
<< static_cast<unsigned>(*result) << '\n';
}
}
{
const auto result = checked_shl(u8{0xFF}, u8{1});
std::cout << "checked_shl(u8(255), 1) = "
<< (result ? "value" : "nullopt (overflow)") << '\n';
}
{
const auto result = checked_shr(u8{1}, u8{8});
std::cout << "checked_shr(u8(1), 8) = "
<< (result ? "value" : "nullopt (overflow)") << '\n';
}

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';
std::cout << "shl<saturate>(u32(max), 1) = "
<< shl<overflow_policy::saturate>(u32{0xFFFFFFFFu}, u32{1}) << '\n';
std::cout << "shr<checked>(u32(8), 1) = ";
{
const auto result = shr<overflow_policy::checked>(u32{8}, u32{1});
if (result)
{
std::cout << *result << '\n';
}
}

return 0;
}
Loading
Loading