diff --git a/doc/modules/ROOT/pages/unsigned_integers.adoc b/doc/modules/ROOT/pages/unsigned_integers.adoc index 23b6528..f8fed01 100644 --- a/doc/modules/ROOT/pages/unsigned_integers.adoc +++ b/doc/modules/ROOT/pages/unsigned_integers.adoc @@ -168,6 +168,22 @@ constexpr T wrapping_div(T lhs, T rhs); template constexpr T wrapping_mod(T lhs, T rhs); +// Strict arithmetic (call std::exit(EXIT_FAILURE) on error) +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; + } // namespace boost::safe_numbers ---- @@ -414,6 +430,36 @@ These functions wrap on overflow without any indication: - `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 |=== @@ -460,6 +506,70 @@ These functions wrap on overflow without any indication: | 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 +|=== + == Constexpr Support All operations are `constexpr`-compatible. diff --git a/include/boost/safe_numbers/detail/overflow_policy.hpp b/include/boost/safe_numbers/detail/overflow_policy.hpp index e0a6b33..0b0c195 100644 --- a/include/boost/safe_numbers/detail/overflow_policy.hpp +++ b/include/boost/safe_numbers/detail/overflow_policy.hpp @@ -14,6 +14,7 @@ enum class overflow_policy overflow_tuple, checked, wrapping, + strict, }; } // namespace boost::safe_numbers::detail diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 27a259a..f04b3df 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -204,6 +205,11 @@ struct add_helper { res = std::numeric_limits::max(); } + else if constexpr (Policy == overflow_policy::strict) + { + static_cast(res); + std::exit(EXIT_FAILURE); + } else { static_cast(res); @@ -336,7 +342,7 @@ struct add_helper template [[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict) { return add_helper::apply(lhs, rhs); } @@ -567,6 +573,11 @@ struct sub_helper { res = std::numeric_limits::min(); } + else if constexpr (Policy == overflow_policy::strict) + { + static_cast(res); + std::exit(EXIT_FAILURE); + } else { static_cast(res); @@ -699,7 +710,7 @@ struct sub_helper template [[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict) { return sub_helper::apply(lhs, rhs); } @@ -811,7 +822,7 @@ struct mul_helper { [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::strict) -> unsigned_integer_basis { using result_type = unsigned_integer_basis; @@ -831,6 +842,11 @@ struct mul_helper { res = std::numeric_limits::max(); } + else if constexpr (Policy == overflow_policy::strict) + { + static_cast(res); + std::exit(EXIT_FAILURE); + } else { static_cast(res); @@ -963,7 +979,7 @@ struct mul_helper template [[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict) { return mul_helper::apply(lhs, rhs); } @@ -996,6 +1012,7 @@ struct div_helper { [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::strict) -> unsigned_integer_basis { using result_type = unsigned_integer_basis; @@ -1010,6 +1027,10 @@ struct div_helper { BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1113,7 +1134,7 @@ struct div_helper template [[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::checked) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { return div_helper::apply(lhs, rhs); } @@ -1146,6 +1167,7 @@ struct mod_helper { [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::strict) -> unsigned_integer_basis { using result_type = unsigned_integer_basis; @@ -1158,7 +1180,11 @@ struct mod_helper } else if constexpr (Policy == overflow_policy::saturate) { - BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero")); + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); } else { @@ -1263,7 +1289,7 @@ struct mod_helper template [[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::checked) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { return mod_helper::apply(lhs, rhs); } @@ -1556,6 +1582,56 @@ template BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping modulo", wrapping_mod) +template +[[nodiscard]] constexpr auto strict_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict addition", strict_add) + +template +[[nodiscard]] constexpr auto strict_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::sub_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict subtraction", strict_sub) + +template +[[nodiscard]] constexpr auto strict_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict multiplication", strict_mul) + +template +[[nodiscard]] constexpr auto strict_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict division", strict_div) + +template +[[nodiscard]] constexpr auto strict_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict modulo", strict_mod) + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP diff --git a/test/Jamfile b/test/Jamfile index 40d6e80..0accc61 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -90,6 +90,11 @@ run test_unsigned_wrapping_subtraction.cpp ; run test_unsigned_wrapping_multiplication.cpp ; run test_unsigned_wrapping_division.cpp ; run test_unsigned_wrapping_mod.cpp ; +run-fail test_unsigned_strict_addition.cpp ; +run-fail test_unsigned_strict_subtraction.cpp ; +run-fail test_unsigned_strict_multiplication.cpp ; +run-fail test_unsigned_strict_division.cpp ; +run-fail test_unsigned_strict_mod.cpp ; # Exhaustive verification tests run test_exhaustive_u8_arithmetic.cpp ; diff --git a/test/test_unsigned_strict_addition.cpp b/test/test_unsigned_strict_addition.cpp new file mode 100644 index 0000000..3bc1239 --- /dev/null +++ b/test/test_unsigned_strict_addition.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_add calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values that will overflow when added + constexpr u32 max_val {std::numeric_limits::max()}; + constexpr u32 one {1U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_add(max_val, one)}; + + // Should never reach here + static_cast(result); + return 0; +} diff --git a/test/test_unsigned_strict_division.cpp b/test/test_unsigned_strict_division.cpp new file mode 100644 index 0000000..596bc42 --- /dev/null +++ b/test/test_unsigned_strict_division.cpp @@ -0,0 +1,32 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_div calls std::exit(EXIT_FAILURE) on division by zero +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values for division by zero + constexpr u32 one {1U}; + constexpr u32 zero {0U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_div(one, zero)}; + + // Should never reach here + static_cast(result); + return 0; +} diff --git a/test/test_unsigned_strict_mod.cpp b/test/test_unsigned_strict_mod.cpp new file mode 100644 index 0000000..ca44242 --- /dev/null +++ b/test/test_unsigned_strict_mod.cpp @@ -0,0 +1,32 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_mod calls std::exit(EXIT_FAILURE) on modulo by zero +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values for modulo by zero + constexpr u32 one {1U}; + constexpr u32 zero {0U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_mod(one, zero)}; + + // Should never reach here + static_cast(result); + return 0; +} diff --git a/test/test_unsigned_strict_multiplication.cpp b/test/test_unsigned_strict_multiplication.cpp new file mode 100644 index 0000000..4fc4a8b --- /dev/null +++ b/test/test_unsigned_strict_multiplication.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_mul calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values that will overflow when multiplied + constexpr u32 max_val {std::numeric_limits::max()}; + constexpr u32 two {2U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_mul(max_val, two)}; + + // Should never reach here + static_cast(result); + return 0; +} diff --git a/test/test_unsigned_strict_subtraction.cpp b/test/test_unsigned_strict_subtraction.cpp new file mode 100644 index 0000000..e6d0f73 --- /dev/null +++ b/test/test_unsigned_strict_subtraction.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_sub calls std::exit(EXIT_FAILURE) on underflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values that will underflow when subtracted + constexpr u32 zero {0U}; + constexpr u32 one {1U}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_sub(zero, one)}; + + // Should never reach here + static_cast(result); + return 0; +}