From c889910c3a1975262da527283faf42068e5057f8 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 5 Feb 2026 14:18:35 -0500 Subject: [PATCH 1/6] Move overflow policy out of detail namespace --- .../safe_numbers/{detail => }/overflow_policy.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename include/boost/safe_numbers/{detail => }/overflow_policy.hpp (51%) diff --git a/include/boost/safe_numbers/detail/overflow_policy.hpp b/include/boost/safe_numbers/overflow_policy.hpp similarity index 51% rename from include/boost/safe_numbers/detail/overflow_policy.hpp rename to include/boost/safe_numbers/overflow_policy.hpp index 0b0c195..7a3dd32 100644 --- a/include/boost/safe_numbers/detail/overflow_policy.hpp +++ b/include/boost/safe_numbers/overflow_policy.hpp @@ -2,10 +2,10 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt -#ifndef BOOST_SAFE_NUMBERS_DETAIL_OVERFLOW_POLICY_HPP -#define BOOST_SAFE_NUMBERS_DETAIL_OVERFLOW_POLICY_HPP +#ifndef BOOST_SAFE_NUMBERS_OVERFLOW_POLICY_HPP +#define BOOST_SAFE_NUMBERS_OVERFLOW_POLICY_HPP -namespace boost::safe_numbers::detail { +namespace boost::safe_numbers { enum class overflow_policy { @@ -17,6 +17,6 @@ enum class overflow_policy strict, }; -} // namespace boost::safe_numbers::detail +} // namespace boost::safe_numbers -#endif // BOOST_SAFE_NUMBERS_DETAIL_OVERFLOW_POLICY_HPP +#endif // BOOST_SAFE_NUMBERS_OVERFLOW_POLICY_HPP From 0ac481ff240fde0f3516d428170eb19b3a7c5c81 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 5 Feb 2026 14:18:48 -0500 Subject: [PATCH 2/6] Add single policy based driver function --- .../detail/unsigned_integer_basis.hpp | 211 +++++++++++++++--- 1 file changed, 185 insertions(+), 26 deletions(-) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index f04b3df..0572fc4 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -1387,7 +1387,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::add_impl(lhs, rhs); + return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating addition", saturating_add) @@ -1397,7 +1397,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::sub_impl(lhs, rhs); + return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating subtraction", saturating_sub) @@ -1407,7 +1407,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::mul_impl(lhs, rhs); + return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating multiplication", saturating_mul) @@ -1417,7 +1417,7 @@ template const detail::unsigned_integer_basis rhs) -> detail::unsigned_integer_basis { - return detail::div_impl(lhs, rhs); + return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating division", saturating_div) @@ -1427,7 +1427,7 @@ template const detail::unsigned_integer_basis rhs) -> detail::unsigned_integer_basis { - return detail::mod_impl(lhs, rhs); + return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating modulo", saturating_mod) @@ -1437,7 +1437,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::pair, bool> { - return detail::add_impl(lhs, rhs); + return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing addition", overflowing_add) @@ -1447,7 +1447,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::pair, bool> { - return detail::sub_impl(lhs, rhs); + return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing subtraction", overflowing_sub) @@ -1457,7 +1457,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::pair, bool> { - return detail::mul_impl(lhs, rhs); + return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing multiplication", overflowing_mul) @@ -1467,7 +1467,7 @@ template const detail::unsigned_integer_basis rhs) -> std::pair, bool> { - return detail::div_impl(lhs, rhs); + return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing division", overflowing_div) @@ -1477,7 +1477,7 @@ template const detail::unsigned_integer_basis rhs) -> std::pair, bool> { - return detail::mod_impl(lhs, rhs); + return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing modulo", overflowing_mod) @@ -1487,7 +1487,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::optional> { - return detail::add_impl(lhs, rhs); + return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked addition", checked_add) @@ -1497,7 +1497,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::optional> { - return detail::sub_impl(lhs, rhs); + return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked subtraction", checked_sub) @@ -1507,7 +1507,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::optional> { - return detail::mul_impl(lhs, rhs); + return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked multiplication", checked_mul) @@ -1517,7 +1517,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::optional> { - return detail::div_impl(lhs, rhs); + return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked division", checked_div) @@ -1527,7 +1527,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> std::optional> { - return detail::mod_impl(lhs, rhs); + return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked modulo", checked_mod) @@ -1537,7 +1537,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::add_impl(lhs, rhs); + return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping addition", wrapping_add) @@ -1547,7 +1547,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::sub_impl(lhs, rhs); + return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping subtraction", wrapping_sub) @@ -1557,7 +1557,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::mul_impl(lhs, rhs); + return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping multiplication", wrapping_mul) @@ -1567,7 +1567,7 @@ template const detail::unsigned_integer_basis rhs) -> detail::unsigned_integer_basis { - return detail::div_impl(lhs, rhs); + return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping division", wrapping_div) @@ -1577,7 +1577,7 @@ template const detail::unsigned_integer_basis rhs) -> detail::unsigned_integer_basis { - return detail::mod_impl(lhs, rhs); + return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping modulo", wrapping_mod) @@ -1587,7 +1587,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::add_impl(lhs, rhs); + return detail::add_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict addition", strict_add) @@ -1597,7 +1597,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::sub_impl(lhs, rhs); + return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict subtraction", strict_sub) @@ -1607,7 +1607,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::mul_impl(lhs, rhs); + return detail::mul_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict multiplication", strict_mul) @@ -1617,7 +1617,7 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::div_impl(lhs, rhs); + return detail::div_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict division", strict_div) @@ -1627,11 +1627,170 @@ template const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { - return detail::mod_impl(lhs, rhs); + return detail::mod_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict modulo", strict_mod) +// ------------------------------ +// Generic policy-parameterized functions +// ------------------------------ + +template +[[nodiscard]] constexpr auto add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs + rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::wrapping) + { + return wrapping_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_add(lhs, rhs); + } +} + +template +[[nodiscard]] constexpr auto sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs - rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::wrapping) + { + return wrapping_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_sub(lhs, rhs); + } +} + +template +[[nodiscard]] constexpr auto mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs * rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::wrapping) + { + return wrapping_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_mul(lhs, rhs); + } +} + +template +[[nodiscard]] constexpr auto div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs / rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::wrapping) + { + return wrapping_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_div(lhs, rhs); + } +} + +template +[[nodiscard]] constexpr auto mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs % rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::wrapping) + { + return wrapping_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_mod(lhs, rhs); + } +} + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP From ac82d66d2333a961172e6a29b364cd0645a47cf6 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 5 Feb 2026 14:18:54 -0500 Subject: [PATCH 3/6] Update docs --- doc/modules/ROOT/nav.adoc | 1 + doc/modules/ROOT/pages/api_reference.adoc | 42 ++- doc/modules/ROOT/pages/policies.adoc | 328 ++++++++++++++++++ doc/modules/ROOT/pages/unsigned_integers.adoc | 270 +------------- 4 files changed, 386 insertions(+), 255 deletions(-) create mode 100644 doc/modules/ROOT/pages/policies.adoc diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index b6ac74d..816dc81 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -13,6 +13,7 @@ ** xref:api_reference.adoc#api_functions[Functions] ** xref:api_reference.adoc#api_headers[Headers] * xref:unsigned_integers.adoc[] +* xref:policies.adoc[] * xref:limits.adoc[] * xref:format.adoc[] * xref:charconv.adoc[] diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index e92eec4..1eb90f3 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -33,6 +33,16 @@ https://www.boost.org/LICENSE_1_0.txt | Safe unsigned 128-bit integer |=== +=== Enumerations + +[cols="1,2", options="header"] +|=== +| Type | Description + +| xref:policies.adoc[`overflow_policy`] +| Enum class specifying the overflow handling policy for arithmetic operations +|=== + [#api_functions] == Functions @@ -42,13 +52,38 @@ https://www.boost.org/LICENSE_1_0.txt |=== | Function | Description -| xref:charconv.adoc#to_chars[`to_chars`] +| xref:charconv.adoc[`to_chars`] | Converts a safe integer to a character string -| xref:charconv.adoc#from_chars[`from_chars`] +| xref:charconv.adoc[`from_chars`] | Parses a character string into a safe integer |=== +=== Arithmetic + +[cols="1,2", options="header"] +|=== +| Function | Description + +| xref:policies.adoc[`saturating_add`, `saturating_sub`, `saturating_mul`, `saturating_div`, `saturating_mod`] +| Saturating arithmetic (clamp to min/max on overflow) + +| xref:policies.adoc[`overflowing_add`, `overflowing_sub`, `overflowing_mul`, `overflowing_div`, `overflowing_mod`] +| Overflowing arithmetic (wrap and return overflow flag) + +| 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) + +| xref:policies.adoc[`add`, `sub`, `mul`, `div`, `mod`] +| Generic policy-parameterized arithmetic (takes `overflow_policy` as template parameter) +|=== + [#api_headers] == Headers @@ -59,6 +94,9 @@ https://www.boost.org/LICENSE_1_0.txt | `` | All unsigned safe integer types (`u8`, `u16`, `u32`, `u64`, `u128`) +| `` +| The `overflow_policy` enum class + | `` | Character conversion functions (`to_chars`, `from_chars`) diff --git a/doc/modules/ROOT/pages/policies.adoc b/doc/modules/ROOT/pages/policies.adoc new file mode 100644 index 0000000..81894d2 --- /dev/null +++ b/doc/modules/ROOT/pages/policies.adoc @@ -0,0 +1,328 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#policies] + += Overflow Policies + +== 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 `overflow_policy` Enum + +[source,c++] +---- +#include + +namespace boost::safe_numbers { + +enum class overflow_policy +{ + throw_exception, // Throw an exception on overflow/underflow + 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 +}; + +} // namespace boost::safe_numbers +---- + +== Policy Summary + +|=== +| Policy | Overflow/Underflow Behavior | Division by Zero | noexcept + +| `throw_exception` (default) +| Throws exception +| Throws `std::domain_error` +| No + +| `saturate` +| Clamps to min/max +| Throws `std::domain_error` +| Add/Sub/Mul: Yes, Div/Mod: No + +| `overflow_tuple` +| Wraps, returns flag +| Throws `std::domain_error` +| Add/Sub/Mul: Yes, Div/Mod: No + +| `checked` +| Returns `std::nullopt` +| 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)` +| Yes +|=== + +== Named Arithmetic Functions + +For cases where throwing exceptions is not desired, named free functions are provided for each policy. + +=== Saturating Arithmetic + +[source,c++] +---- +template +constexpr T saturating_add(T lhs, T rhs) noexcept; + +template +constexpr T saturating_sub(T lhs, T rhs) noexcept; + +template +constexpr T saturating_mul(T lhs, T rhs) noexcept; + +template +constexpr T saturating_div(T lhs, T rhs); + +template +constexpr T saturating_mod(T lhs, T rhs); +---- + +These functions clamp the result to the representable range instead of throwing: + +- `saturating_add`: Returns the sum, saturating at `std::numeric_limits::max()` on overflow +- `saturating_sub`: Returns the difference, saturating at `std::numeric_limits::min()` (zero) on underflow +- `saturating_mul`: Returns the product, saturating at `std::numeric_limits::max()` on overflow +- `saturating_div`: Returns the quotient; throws `std::domain_error` on division by zero (overflow is impossible) +- `saturating_mod`: Returns the remainder; throws `std::domain_error` on division by zero (overflow is impossible) + +=== Overflowing Arithmetic + +[source,c++] +---- +template +constexpr std::pair overflowing_add(T lhs, T rhs) noexcept; + +template +constexpr std::pair overflowing_sub(T lhs, T rhs) noexcept; + +template +constexpr std::pair overflowing_mul(T lhs, T rhs) noexcept; + +template +constexpr std::pair overflowing_div(T lhs, T rhs); + +template +constexpr std::pair overflowing_mod(T lhs, T rhs); +---- + +These functions provide well-defined wrapping semantics with a flag to indicate if overflow occurred. +This follows normal C family unsigned rollover where `UINT_MAX + 1 == 0` and `0 - 1 == UINT_MAX`. + +- `overflowing_add`: Returns the wrapped sum and `true` if overflow occurred +- `overflowing_sub`: Returns the wrapped difference and `true` if underflow occurred +- `overflowing_mul`: Returns the wrapped product and `true` if overflow occurred +- `overflowing_div`: Returns the quotient and `false`; throws `std::domain_error` on division by zero +- `overflowing_mod`: Returns the remainder and `false`; throws `std::domain_error` on division by zero + +=== Checked Arithmetic + +[source,c++] +---- +template +constexpr std::optional checked_add(T lhs, T rhs) noexcept; + +template +constexpr std::optional checked_sub(T lhs, T rhs) noexcept; + +template +constexpr std::optional checked_mul(T lhs, T rhs) noexcept; + +template +constexpr std::optional checked_div(T lhs, T rhs) noexcept; + +template +constexpr std::optional checked_mod(T lhs, T rhs) noexcept; +---- + +These functions return `std::nullopt` on overflow, underflow, or division by zero: + +- `checked_add`: Returns the sum, or `std::nullopt` on overflow +- `checked_sub`: Returns the difference, or `std::nullopt` on underflow +- `checked_mul`: Returns the product, or `std::nullopt` on overflow +- `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 +constexpr T wrapping_add(T lhs, T rhs) noexcept; + +template +constexpr T wrapping_sub(T lhs, T rhs) noexcept; + +template +constexpr T wrapping_mul(T lhs, T rhs) noexcept; + +template +constexpr T wrapping_div(T lhs, T rhs); + +template +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++] +---- +template +constexpr T strict_add(T lhs, T rhs) noexcept; + +template +constexpr T strict_sub(T lhs, T rhs) noexcept; + +template +constexpr T strict_mul(T lhs, T rhs) noexcept; + +template +constexpr T strict_div(T lhs, T rhs) noexcept; + +template +constexpr T strict_mod(T lhs, T rhs) noexcept; +---- + +These functions call `std::exit(EXIT_FAILURE)` on error, providing a hard termination policy for safety-critical applications where exceptions cannot be used: + +- `strict_add`: Returns the sum; calls `std::exit(EXIT_FAILURE)` on overflow +- `strict_sub`: Returns the difference; calls `std::exit(EXIT_FAILURE)` on underflow +- `strict_mul`: Returns the product; calls `std::exit(EXIT_FAILURE)` on overflow +- `strict_div`: Returns the quotient; calls `std::exit(EXIT_FAILURE)` on division by zero +- `strict_mod`: Returns the remainder; calls `std::exit(EXIT_FAILURE)` on modulo by zero + +All strict functions are marked `noexcept` since `std::exit` does not throw. + +== Generic Policy-Parameterized Arithmetic + +[source,c++] +---- +template +constexpr auto add(T lhs, T rhs); + +template +constexpr auto sub(T lhs, T rhs); + +template +constexpr auto mul(T lhs, T rhs); + +template +constexpr auto div(T lhs, T rhs); + +template +constexpr auto mod(T lhs, T rhs); +---- + +These functions accept an `overflow_policy` as a template parameter and dispatch to the corresponding named function. +The return type depends on the policy: + +|=== +| Policy | Return Type + +| `overflow_policy::throw_exception` +| `T` + +| `overflow_policy::saturate` +| `T` + +| `overflow_policy::overflow_tuple` +| `std::pair` + +| `overflow_policy::checked` +| `std::optional` + +| `overflow_policy::wrapping` +| `T` + +| `overflow_policy::strict` +| `T` +|=== + +This allows writing generic code parameterized on the overflow policy: + +[source,c++] +---- +using namespace boost::safe_numbers; + +// The policy can be a template parameter of your own function +template +auto compute(u32 a, u32 b) +{ + return add(a, b); +} + +auto result_sat = compute(u32{100}, u32{200}); +auto result_chk = compute(u32{100}, u32{200}); +---- + +== Exception Summary + +The default operators and some named functions throw exceptions on error: + +|=== +| Operation | Exception Type | Condition + +| `pass:[+]`, `pass:[+=]` +| `std::overflow_error` +| Result exceeds maximum value + +| `-`, `-=` +| `std::underflow_error` +| Result would be negative + +| `pass:[*]`, `pass:[*=]` +| `std::overflow_error` +| Result exceeds maximum value + +| `/`, `/=` +| `std::domain_error` +| Division by zero + +| `%`, `%=` +| `std::domain_error` +| Modulo by zero + +| `++` (pre/post) +| `std::overflow_error` +| Value is at maximum + +| `--` (pre/post) +| `std::underflow_error` +| Value is zero + +| `saturating_div`, `saturating_mod` +| `std::domain_error` +| Division by zero + +| `overflowing_div`, `overflowing_mod` +| `std::domain_error` +| Division by zero + +| `wrapping_div`, `wrapping_mod` +| `std::domain_error` +| Division by zero +|=== diff --git a/doc/modules/ROOT/pages/unsigned_integers.adoc b/doc/modules/ROOT/pages/unsigned_integers.adoc index f8fed01..882bbe5 100644 --- a/doc/modules/ROOT/pages/unsigned_integers.adoc +++ b/doc/modules/ROOT/pages/unsigned_integers.adoc @@ -184,6 +184,22 @@ constexpr T strict_div(T lhs, T rhs) noexcept; template constexpr T strict_mod(T lhs, T rhs) noexcept; +// Generic policy-parameterized arithmetic +template +constexpr auto add(T lhs, T rhs); + +template +constexpr auto sub(T lhs, T rhs); + +template +constexpr auto mul(T lhs, T rhs); + +template +constexpr auto div(T lhs, T rhs); + +template +constexpr auto mod(T lhs, T rhs); + } // namespace boost::safe_numbers ---- @@ -316,259 +332,7 @@ To perform operations between different widths, explicitly convert to the same t == Alternative Arithmetic Functions For cases where throwing exceptions is not desired, alternative arithmetic functions are provided with different overflow handling policies. - -=== Saturating Arithmetic - -[source,c++] ----- -template -constexpr T saturating_add(T lhs, T rhs) noexcept; - -template -constexpr T saturating_sub(T lhs, T rhs) noexcept; - -template -constexpr T saturating_mul(T lhs, T rhs) noexcept; - -template -constexpr T saturating_div(T lhs, T rhs); - -template -constexpr T saturating_mod(T lhs, T rhs); ----- - -These functions clamp the result to the representable range instead of throwing: - -- `saturating_add`: Returns the sum, saturating at `std::numeric_limits::max()` on overflow -- `saturating_sub`: Returns the difference, saturating at `std::numeric_limits::min()` (zero) on underflow -- `saturating_mul`: Returns the product, saturating at `std::numeric_limits::max()` on overflow -- `saturating_div`: Returns the quotient; throws `std::domain_error` on division by zero (overflow is impossible) -- `saturating_mod`: Returns the remainder; throws `std::domain_error` on division by zero (overflow is impossible) - -=== Overflowing Arithmetic - -[source,c++] ----- -template -constexpr std::pair overflowing_add(T lhs, T rhs) noexcept; - -template -constexpr std::pair overflowing_sub(T lhs, T rhs) noexcept; - -template -constexpr std::pair overflowing_mul(T lhs, T rhs) noexcept; - -template -constexpr std::pair overflowing_div(T lhs, T rhs); - -template -constexpr std::pair overflowing_mod(T lhs, T rhs); ----- - -These functions provide well-defined wrapping semantics with a flag to indicate if overflow occurred. -This follows normal C family unsigned rollover where `UINT_MAX + 1 == 0` and `0 - 1 == UINT_MAX`. - -- `overflowing_add`: Returns the wrapped sum and `true` if overflow occurred -- `overflowing_sub`: Returns the wrapped difference and `true` if underflow occurred -- `overflowing_mul`: Returns the wrapped product and `true` if overflow occurred -- `overflowing_div`: Returns the quotient and `false`; throws `std::domain_error` on division by zero -- `overflowing_mod`: Returns the remainder and `false`; throws `std::domain_error` on division by zero - -=== Checked Arithmetic - -[source,c++] ----- -template -constexpr std::optional checked_add(T lhs, T rhs) noexcept; - -template -constexpr std::optional checked_sub(T lhs, T rhs) noexcept; - -template -constexpr std::optional checked_mul(T lhs, T rhs) noexcept; - -template -constexpr std::optional checked_div(T lhs, T rhs) noexcept; - -template -constexpr std::optional checked_mod(T lhs, T rhs) noexcept; ----- - -These functions return `std::nullopt` on overflow, underflow, or division by zero: - -- `checked_add`: Returns the sum, or `std::nullopt` on overflow -- `checked_sub`: Returns the difference, or `std::nullopt` on underflow -- `checked_mul`: Returns the product, or `std::nullopt` on overflow -- `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 -constexpr T wrapping_add(T lhs, T rhs) noexcept; - -template -constexpr T wrapping_sub(T lhs, T rhs) noexcept; - -template -constexpr T wrapping_mul(T lhs, T rhs) noexcept; - -template -constexpr T wrapping_div(T lhs, T rhs); - -template -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++] ----- -template -constexpr T strict_add(T lhs, T rhs) noexcept; - -template -constexpr T strict_sub(T lhs, T rhs) noexcept; - -template -constexpr T strict_mul(T lhs, T rhs) noexcept; - -template -constexpr T strict_div(T lhs, T rhs) noexcept; - -template -constexpr T strict_mod(T lhs, T rhs) noexcept; ----- - -These functions call `std::exit(EXIT_FAILURE)` on error, providing a hard termination policy for safety-critical applications where exceptions cannot be used: - -- `strict_add`: Returns the sum; calls `std::exit(EXIT_FAILURE)` on overflow -- `strict_sub`: Returns the difference; calls `std::exit(EXIT_FAILURE)` on underflow -- `strict_mul`: Returns the product; calls `std::exit(EXIT_FAILURE)` on overflow -- `strict_div`: Returns the quotient; calls `std::exit(EXIT_FAILURE)` on division by zero -- `strict_mod`: Returns the remainder; calls `std::exit(EXIT_FAILURE)` on modulo by zero - -All strict functions are marked `noexcept` since `std::exit` does not throw. - -== Exception Summary - -|=== -| Operation | Exception Type | Condition - -| `pass:[+]`, `pass:[+=]` -| `std::overflow_error` -| Result exceeds maximum value - -| `-`, `-=` -| `std::underflow_error` -| Result would be negative - -| `pass:[*]`, `pass:[*=]` -| `std::overflow_error` -| Result exceeds maximum value - -| `/`, `/=` -| `std::domain_error` -| Division by zero - -| `%`, `%=` -| `std::domain_error` -| Modulo by zero - -| `++` (pre/post) -| `std::overflow_error` -| Value is at maximum - -| `--` (pre/post) -| `std::underflow_error` -| Value is zero - -| `saturating_div`, `saturating_mod` -| `std::domain_error` -| Division by zero - -| `overflowing_div`, `overflowing_mod` -| `std::domain_error` -| Division by zero - -| `wrapping_div`, `wrapping_mod` -| `std::domain_error` -| Division by zero -|=== - -== Strict Functions Behavior - -The `strict_*` functions do not throw exceptions. Instead, they call `std::exit(EXIT_FAILURE)` on error: - -|=== -| Operation | Behavior | Condition - -| `strict_add` -| `std::exit(EXIT_FAILURE)` -| Overflow - -| `strict_sub` -| `std::exit(EXIT_FAILURE)` -| Underflow - -| `strict_mul` -| `std::exit(EXIT_FAILURE)` -| Overflow - -| `strict_div` -| `std::exit(EXIT_FAILURE)` -| Division by zero - -| `strict_mod` -| `std::exit(EXIT_FAILURE)` -| Modulo by zero -|=== - -== Policy Summary - -|=== -| Policy | Overflow/Underflow Behavior | Division by Zero | noexcept - -| Default operators -| Throws exception -| Throws `std::domain_error` -| No - -| `saturating_*` -| Clamps to min/max -| Throws `std::domain_error` -| Add/Sub/Mul: Yes, Div/Mod: No - -| `overflowing_*` -| Wraps, returns flag -| Throws `std::domain_error` -| Add/Sub/Mul: Yes, Div/Mod: No - -| `checked_*` -| Returns `std::nullopt` -| 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)` -| Yes -|=== +See xref:policies.adoc[] for full documentation of all policies, named arithmetic functions, and the generic policy-parameterized interface. == Constexpr Support From 331a3a40c6cf7af99726dc576c087fd023212340 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 5 Feb 2026 14:34:43 -0500 Subject: [PATCH 4/6] Add strict arithmetic example --- examples/strict_arithmetic.cpp | 66 ++++++++++++++++++++++++++++++++++ test/Jamfile | 1 + 2 files changed, 67 insertions(+) create mode 100644 examples/strict_arithmetic.cpp diff --git a/examples/strict_arithmetic.cpp b/examples/strict_arithmetic.cpp new file mode 100644 index 0000000..1f6f3c2 --- /dev/null +++ b/examples/strict_arithmetic.cpp @@ -0,0 +1,66 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[strict_arithmetic_example +//` This example demonstrates the use of strict arithmetic operations. +//` These functions call std::exit(EXIT_FAILURE) on overflow, underflow, +//` or division by zero. They are designed for safety-critical applications +//` where exceptions cannot be used but silent wrapping is unacceptable. + +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u32; + using boost::safe_numbers::strict_add; + using boost::safe_numbers::strict_sub; + using boost::safe_numbers::strict_mul; + using boost::safe_numbers::strict_div; + using boost::safe_numbers::strict_mod; + + // Normal operations that don't overflow work as expected + { + const u32 a {100U}; + const u32 b {50U}; + + std::cout << "strict_add(100, 50) = " << strict_add(a, b) << std::endl; + std::cout << "strict_sub(100, 50) = " << strict_sub(a, b) << std::endl; + std::cout << "strict_mul(100, 50) = " << strict_mul(a, b) << std::endl; + std::cout << "strict_div(100, 50) = " << strict_div(a, b) << std::endl; + std::cout << "strict_mod(100, 50) = " << strict_mod(a, b) << std::endl; + } + + // Strict arithmetic is noexcept - safe for -fno-exceptions environments + { + const u32 a {1000000U}; + const u32 b {3U}; + + // These are guaranteed to never throw + static_assert(noexcept(strict_add(a, b))); + static_assert(noexcept(strict_sub(a, b))); + static_assert(noexcept(strict_mul(a, b))); + static_assert(noexcept(strict_div(a, b))); + static_assert(noexcept(strict_mod(a, b))); + + std::cout << "strict_div(1000000, 3) = " << strict_div(a, b) << std::endl; + std::cout << "strict_mod(1000000, 3) = " << strict_mod(a, b) << std::endl; + } + + // NOTE: If any of these operations would overflow, underflow, or + // divide by zero, the program would immediately terminate via + // std::exit(EXIT_FAILURE). For example: + // + // u32 max_val {std::numeric_limits::max()}; + // strict_add(max_val, u32{1U}); // Terminates the program + // + // strict_sub(u32{0U}, u32{1U}); // Terminates the program + // + // strict_div(u32{1U}, u32{0U}); // Terminates the program + + return 0; +} +//] diff --git a/test/Jamfile b/test/Jamfile index 0accc61..529e508 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -118,5 +118,6 @@ run ../examples/saturating_arithmetic.cpp ; run ../examples/overflowing_arithmetic.cpp ; run ../examples/checked_arithmetic.cpp ; run ../examples/wrapping_arithmetic.cpp ; +run ../examples/strict_arithmetic.cpp ; run ../examples/fmt_format.cpp ; run ../examples/charconv.cpp ; From 371186f2e90236baab16f1901a13a5a1023213df Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 5 Feb 2026 14:34:52 -0500 Subject: [PATCH 5/6] Add generic example --- examples/generic_arithmetic.cpp | 81 +++++++++++++++++++++++++++++++++ test/Jamfile | 1 + 2 files changed, 82 insertions(+) create mode 100644 examples/generic_arithmetic.cpp diff --git a/examples/generic_arithmetic.cpp b/examples/generic_arithmetic.cpp new file mode 100644 index 0000000..3576d28 --- /dev/null +++ b/examples/generic_arithmetic.cpp @@ -0,0 +1,81 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[generic_arithmetic_example +//` This example demonstrates the generic policy-parameterized arithmetic +//` functions (add, sub, mul, div, mod). These accept an overflow_policy +//` as a template parameter, allowing you to write code that is generic +//` over the overflow handling strategy. + +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + using boost::safe_numbers::u32; + using boost::safe_numbers::overflow_policy; + using boost::safe_numbers::add; + using boost::safe_numbers::sub; + using boost::safe_numbers::mul; + + const u32 a {100U}; + const u32 b {50U}; + + // Same operation, different policies via the generic interface + { + const auto throwing {add(a, b)}; + const auto saturated {add(a, b)}; + const auto wrapped {add(a, b)}; + const auto strict {add(a, b)}; + + std::cout << "add(100, 50) = " << throwing << std::endl; + std::cout << "add(100, 50) = " << saturated << std::endl; + std::cout << "add(100, 50) = " << wrapped << std::endl; + std::cout << "add(100, 50) = " << strict << std::endl; + } + + // Policies with different return types + { + // overflow_tuple returns std::pair + const auto [result_ot, overflowed] {add(a, b)}; + std::cout << "add(100, 50) = " << result_ot + << " (overflowed: " << std::boolalpha << overflowed << ")" << std::endl; + + // checked returns std::optional + const auto result_chk {add(a, b)}; + if (result_chk) + { + std::cout << "add(100, 50) = " << *result_chk << std::endl; + } + } + + // The real power: writing generic algorithms parameterized on policy + { + const u32 max_val {std::numeric_limits::max()}; + const u32 one {1U}; + + // checked policy returns nullopt on overflow + const auto checked_result {add(max_val, one)}; + std::cout << "add(max, 1) = " + << (checked_result ? "has value" : "nullopt (overflow)") + << std::endl; + + // saturate policy clamps to max + const auto sat_result {add(max_val, one)}; + std::cout << "add(max, 1) = " << sat_result << std::endl; + + // wrapping policy wraps around + const auto wrap_result {add(max_val, one)}; + std::cout << "add(max, 1) = " << wrap_result << std::endl; + } + + return 0; +} +//] diff --git a/test/Jamfile b/test/Jamfile index 529e508..5be6ce0 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -119,5 +119,6 @@ run ../examples/overflowing_arithmetic.cpp ; run ../examples/checked_arithmetic.cpp ; run ../examples/wrapping_arithmetic.cpp ; run ../examples/strict_arithmetic.cpp ; +run ../examples/generic_arithmetic.cpp ; run ../examples/fmt_format.cpp ; run ../examples/charconv.cpp ; From 6746e1f589b677a132aa3415e9e1255b582a9061 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 5 Feb 2026 14:35:01 -0500 Subject: [PATCH 6/6] Add to examples doc and nav --- doc/modules/ROOT/nav.adoc | 2 + doc/modules/ROOT/pages/examples.adoc | 63 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 816dc81..7150ddf 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -6,6 +6,8 @@ ** 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_charconv[Character Conversion] ** xref:examples.adoc#examples_fmt_format[Formatting] * xref:api_reference.adoc[] diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index f6fad9b..381ddf2 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -150,6 +150,59 @@ Counter sequence: 254 255 0 1 2 ---- ==== +[#examples_strict] +== Strict Arithmetic + +Strict arithmetic calls `std::exit(EXIT_FAILURE)` on overflow, underflow, or division by zero. +This provides a hard termination policy for safety-critical applications where exceptions cannot be used but silent wrapping is unacceptable. +All strict functions are `noexcept`. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/strict_arithmetic.cpp[example] demonstrates strict arithmetic operations. +==== +[source, c++] +---- +include::example$strict_arithmetic.cpp[] +---- + +Output: +---- +strict_add(100, 50) = 150 +strict_sub(100, 50) = 50 +strict_mul(100, 50) = 5000 +strict_div(100, 50) = 2 +strict_mod(100, 50) = 0 +strict_div(1000000, 3) = 333333 +strict_mod(1000000, 3) = 1 +---- +==== + +[#examples_generic] +== Generic Policy-Parameterized Arithmetic + +The generic `add`, `sub`, `mul`, `div`, and `mod` functions accept an `overflow_policy` as a template parameter, allowing you to write code that is generic over the overflow handling strategy. +The return type varies by policy. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/generic_arithmetic.cpp[example] demonstrates the generic policy-parameterized interface. +==== +[source, c++] +---- +include::example$generic_arithmetic.cpp[] +---- + +Output: +---- +add(100, 50) = 150 +add(100, 50) = 150 +add(100, 50) = 150 +add(100, 50) = 150 +add(100, 50) = 150 (overflowed: false) +add(100, 50) = 150 +add(max, 1) = nullopt (overflow) +add(max, 1) = 4294967295 +add(max, 1) = 0 +---- +==== + [#examples_charconv] == Character Conversion @@ -252,4 +305,14 @@ The following table summarizes the behavior of each arithmetic policy: |Wraps around (modulo 2^N) |`T` |Counters, checksums, hashing + +|Strict +|Calls `std::exit(EXIT_FAILURE)` +|`T` +|Safety-critical, no-exceptions environments + +|Generic (`add`) +|Depends on policy +|Depends on policy +|Policy-generic algorithms |===