From 5993ae59c021a21c553e8557a129b4491f67abb1 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 19 Dec 2025 09:44:22 +0800 Subject: [PATCH] feat: add strict_* family for safe numeric op. - add strict_* function family for same numeric operation. this function family recently become stable in Rust. - add corresponding test. --- src/yycc/num/safe_op.hpp | 76 ++++++++++++++++++++++++++++++++++- test/yycc/num/safe_op.cpp | 83 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/yycc/num/safe_op.hpp b/src/yycc/num/safe_op.hpp index 9ce687f..664c942 100644 --- a/src/yycc/num/safe_op.hpp +++ b/src/yycc/num/safe_op.hpp @@ -32,7 +32,7 @@ * @details * After writing some programs in Rust, I've deeply realized the richness of operators for primitive types in Rust. * You can explicitly specify the behavior of arithmetic overflow - * (choose one of wrapping, checked, overflowing, and saturating). + * (choose one of wrapping, checked, overflowing, saturating and strict). * Therefore, I'm replicating these convenient features from Rust in this namespace. * * Additionally, I provide a bunch of extra operations, called ordinary operation. @@ -481,6 +481,80 @@ namespace yycc::num::safe_op { #pragma endregion +#pragma region Strict operations + + // YYC MARK: + // If overflow occurs when using strict_* function family, + // these functions will throw exception, otherwise return the computed result. + + /** + * @brief Performs a strict addition operation on two integers. + * @tparam T Integer type. + * @param[in] a The left operand of the addition. + * @param[in] b The right operand of the addition. + * @return The result if no overflow occurs. + * @exception std::overflow_error Overflow occurs when perform operation. + */ + template + requires std::integral + std::optional strict_add(T a, T b) { + T result; + if (hardware_add_overflow(a, b, &result)) throw std::overflow_error("overflow or underflow in strict_add"); + return result; + } + + /** + * @brief Performs a strict subtraction operation on two integers. + * @tparam T Integer type. + * @param[in] a The left operand of the subtraction. + * @param[in] b The right operand of the subtraction. + * @return The result if no overflow occurs. + * @exception std::overflow_error Overflow occurs when perform operation. + */ + template + requires std::integral + std::optional strict_sub(T a, T b) { + T result; + if (hardware_sub_overflow(a, b, &result)) throw std::overflow_error("overflow or underflow in strict_sub"); + return result; + } + + /** + * @brief Performs a strict multiplication operation on two integers. + * @tparam T Integer type. + * @param[in] a The left operand of the multiplication. + * @param[in] b The right operand of the multiplication. + * @return The result if no overflow occurs. + * @exception std::overflow_error Overflow occurs when perform operation. + */ + template + requires std::integral + std::optional strict_mul(T a, T b) { + T result; + if (hardware_mul_overflow(a, b, &result)) throw std::overflow_error("overflow or underflow in strict_mul"); + return result; + } + + /** + * @brief Performs a strict division operation on two integers. + * @tparam T Integer type. + * @param[in] a The left operand of the division. + * @param[in] b The right operand of the division. + * @return The result if no error occurs. + * @exception std::overflow_error Overflow or division by zero occurs when perform operation. + */ + template + requires std::integral + std::optional strict_div(T a, T b) { + // Division by zero is undefined behavior. + if (ub_div_zero(a, b)) throw std::overflow_error("division by zero in strict_div"); + // `INT_MIN / -1` overflow undefined behavior. + if (ub_signed_int_min_div_minus_one(a, b)) throw std::overflow_error("overflow or underflow in strict_div"); + return a / b; + } + +#pragma endregion + #pragma region Overflowing operations // YYC MARK: diff --git a/test/yycc/num/safe_op.cpp b/test/yycc/num/safe_op.cpp index e1d8b70..8b94f85 100644 --- a/test/yycc/num/safe_op.cpp +++ b/test/yycc/num/safe_op.cpp @@ -155,6 +155,89 @@ namespace yycctest::num::safe_op { #pragma endregion +#pragma region Strict operations + + TEST(NumSafeOp, StrictAdd) { + // Unsigned + { + auto rv = OP::strict_add(MAX - 2, 1); + EXPECT_EQ(rv, MAX - 1); + } + { + EXPECT_ANY_THROW(OP::strict_add(MAX - 2, 3)); + } + // Signed + { + auto rv = OP::strict_add(MAX - 2, 1); + EXPECT_EQ(rv, MAX - 1); + } + { + EXPECT_ANY_THROW(OP::strict_add(MAX - 2, 3)); + } + } + + TEST(NumSafeOp, StrictSub) { + // Unsigned + { + auto rv = OP::strict_sub(1, 1); + EXPECT_EQ(rv, 0); + } + { + EXPECT_ANY_THROW(OP::strict_sub(0, 1)); + } + // Signed + { + auto rv = OP::strict_sub(MIN + 2, 1); + EXPECT_EQ(rv, MIN + 1); + } + { + EXPECT_ANY_THROW(OP::strict_sub(MIN + 2, 3)); + } + } + + TEST(NumSafeOp, StrictMul) { + // Unsigned + { + auto rv = OP::strict_mul(5, 1); + EXPECT_EQ(rv, 5); + } + { + EXPECT_ANY_THROW(OP::strict_mul(MAX, 2)); + } + // Signed + { + auto rv = OP::strict_mul(MAX, 1); + EXPECT_EQ(rv, MAX); + } + { + EXPECT_ANY_THROW(OP::strict_mul(MAX, 2)); + } + } + + TEST(NumSafeOp, StrictDiv) { + // Unsigned + { + auto rv = OP::strict_div(128, 2); + EXPECT_EQ(rv, 64); + } + { + EXPECT_ANY_THROW(OP::strict_div(1, 0)); + } + // Signed + { + auto rv = OP::strict_div(MIN + 1, -1); + EXPECT_EQ(rv, INT32_C(2147483647)); + } + { + EXPECT_ANY_THROW(OP::strict_div(MIN, -1)); + } + { + EXPECT_ANY_THROW(OP::strict_div(1, 0)); + } + } + +#pragma endregion + #pragma region Overflowing operations TEST(NumSafeOp, OverflowingAdd) {