#pragma once #include "../macro/os_detector.hpp" #include "../macro/compiler_detector.hpp" #include #include #include #include // Choose the function family for hardware based overflow. #if defined(YYCC_CC_GCC) || defined(YYCC_CC_CLANG) #define YYCC_HARDWARE_OVERFLOW_GCC_FNS #elif defined(YYCC_OS_WINDOWS) #define YYCC_HARDWARE_OVERFLOW_WIN32_FNS #else #error "Not supported platform or compiler for integral overflow function family." #endif // Import essential header if we are using Windows function family. #if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS) // YYC MARK: // This macro is crucial for including "intsafe.h" // Without this, "intsafe.h" will not enable signed integral operations. #define ENABLE_INTSAFE_SIGNED_FUNCTIONS #include "../windows/import_guard_head.hpp" #include #include "../windows/import_guard_tail.hpp" #endif /** * @brief The namespace providing Rust-like safe arithmetic operations. * @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). * Therefore, I'm replicating these convenient features from Rust in this namespace. * * Additionally, I provide a bunch of extra operations, called ordinary operation. * These functions are just an alias to wrapping operator, indicating wrapping is the normal case. * These normal operators will not have any undefined behavior which C++ will make. * It basically like \c Add, \c Sub, \c Mul, and \c Div traits in Rust, * providing safe operations for primitive types. */ namespace yycc::num::safe_op { /* Implementation notes: - Wrapping operation: - Unsigned integer use default overflow behavior. - Signed integer will be casted into unsigned integer before operation to simulate wrapping overflow. - Checked operation: - Use compiler built-in function to detect overflow, return std::optional. - Return std::nullopt when division by zero or overflow. - Overflowing operation: - Return std::pair where bool indicates whether overflow occurs. - Explicitly handle 2 division undefined behavior: - Division by zero. - Signed minimum value divided by -1. - Saturating operation: - Return maximum or minimum value when overflow or underflow occurs. - Determine saturation direction (overflow or underflow) based on operand sign. */ #pragma region Undefined Behaviors /* YYC MARK: Following undefined behaviors should be noticed: - Signed integer overflow and underflow (e.g. INT_MAX + 1). - Perform `INT_MIN / -1` division. - The right operand in division is zero. */ /** * @private * @brief Adds two numbers while considering the undefined behavior of signed integer overflow. * @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 of the addition that takes into account the undefined behavior of signed integers. */ template requires std::integral T ub_signed_int_add(T a, T b) { if constexpr (std::is_unsigned_v) { // Add, Sub, Mul and Div for unsigned integer is natural wrapping. // So we can use operator simply and directly. return a + b; } else { // In C++, it is undefined behavior that signed integer overflow. // So we need cast them into unsigned integer forcely before operation, // do operation for them, and cast the result back to simulate wrapping overflow. using UT = std::make_unsigned_t; return static_cast(static_cast(a) + static_cast(b)); } } /** * @private * @brief Subtracts two numbers while considering the undefined behavior of signed integer overflow. * @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 of the subtraction that takes into account the undefined behavior of signed integers. */ template requires std::integral T ub_signed_int_sub(T a, T b) { if constexpr (std::is_unsigned_v) { return a - b; } else { using UT = std::make_unsigned_t; return static_cast(static_cast(a) - static_cast(b)); } } /** * @private * @brief Multiplies two numbers while considering the undefined behavior of signed integer overflow. * @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 of the multiplication that takes into account the undefined behavior of signed integers. */ template requires std::integral T ub_signed_int_mul(T a, T b) { if constexpr (std::is_unsigned_v) { return a * b; } else { using UT = std::make_unsigned_t; return static_cast(static_cast(a) * static_cast(b)); } } /** * @private * @brief Checks for undefined behavior when dividing the minimum signed integer value by -1. * @tparam T Integer type * @param[in] a The left operand of the division * @param[in] b The right operand of the division * @return Returns true if undefined behavior will occur, false otherwise. */ template requires std::integral bool ub_signed_int_min_div_minus_one(T a, T b) { if constexpr (std::is_signed_v) { // For signed value, `INT_MIN / -1` may cause overflow, // which finally cause the undefined behavior, // due to the truth that `INT_MIN == -INT_MIN - 1`. if (b == -1 && a == std::numeric_limits::min()) { return true; } else { return false; } } else { return false; } } /** * @private * @brief Checks for the undefined behavior of division by zero. * @tparam T Integer type * @param[in] a The left operand of the division * @param[in] b The right operand of the division * @return Returns true if undefined behavior will occur, false otherwise. */ template requires std::integral bool ub_div_zero([[maybe_unused]] T a, T b) { return b == 0; } #pragma endregion #pragma region Hardware Operation Overflow // YYC MARK: // If we are using Windows function family, // we define a convenient macro assisting overflow calculation. #if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS) #define WIN_EASY_OPER(fn, ty, a, b, c) FAILED(fn(static_cast(a), static_cast(b), reinterpret_cast(c))) #endif /** * @private * @brief Addition with overflow detection based on hardware instructions. * @param[in] a The left operand of the addition. * @param[in] b The right operand of the addition. * @param[out] c The pointer to the variable storing result. * @return Returns true if an overflow occurs, false otherwise. */ template requires std::integral bool hardware_add_overflow(T a, T b, T* c) { if (c == nullptr) [[unlikely]] throw std::logic_error("invalid nullptr"); #if defined(YYCC_HARDWARE_OVERFLOW_GCC_FNS) return __builtin_add_overflow(a, b, c); #else bool overflow = false; constexpr size_t T_SIZE = sizeof(T); if constexpr (std::is_signed_v) { if constexpr (T_SIZE == 8) { overflow = WIN_EASY_OPER(LongLongAdd, LONGLONG, a, b, c); } else if constexpr (T_SIZE == 4) { overflow = WIN_EASY_OPER(LongAdd, LONG, a, b, c); } else if constexpr (T_SIZE == 2) { overflow = WIN_EASY_OPER(ShortAdd, SHORT, a, b, c); } else if constexpr (T_SIZE == 1) { overflow = WIN_EASY_OPER(Int8Add, INT8, a, b, c); } else { static_assert(std::false_type::value, "not supported integral type."); } } else { if constexpr (T_SIZE == 8) { overflow = WIN_EASY_OPER(ULongLongAdd, ULONGLONG, a, b, c); } else if constexpr (T_SIZE == 4) { overflow = WIN_EASY_OPER(ULongAdd, ULONG, a, b, c); } else if constexpr (T_SIZE == 2) { overflow = WIN_EASY_OPER(UShortAdd, USHORT, a, b, c); } else if constexpr (T_SIZE == 1) { overflow = WIN_EASY_OPER(UInt8Add, UINT8, a, b, c); } else { static_assert(std::false_type::value, "not supported integral type."); } } // Due to the limitation of Windows function family, // if overflow or underflow occurs, there is no calculation result. // So we need fill the wrapping value manually. if (overflow) *c = ub_signed_int_add(a, b); return overflow; #endif } /** * @private * @brief Subtraction with overflow detection based on hardware instructions. * @param[in] a The left operand of the subtraction. * @param[in] b The right operand of the subtraction. * @param[out] c The pointer to the variable storing the result. * @return Returns true if an overflow occurs, false otherwise. */ template requires std::integral bool hardware_sub_overflow(T a, T b, T* c) { if (c == nullptr) [[unlikely]] throw std::logic_error("invalid nullptr"); #if defined(YYCC_HARDWARE_OVERFLOW_GCC_FNS) return __builtin_sub_overflow(a, b, c); #else bool overflow = false; constexpr size_t T_SIZE = sizeof(T); if constexpr (std::is_signed_v) { if constexpr (T_SIZE == 8) { overflow = WIN_EASY_OPER(LongLongSub, LONGLONG, a, b, c); } else if constexpr (T_SIZE == 4) { overflow = WIN_EASY_OPER(LongSub, LONG, a, b, c); } else if constexpr (T_SIZE == 2) { overflow = WIN_EASY_OPER(ShortSub, SHORT, a, b, c); } else if constexpr (T_SIZE == 1) { overflow = WIN_EASY_OPER(Int8Sub, INT8, a, b, c); } else { static_assert(std::false_type::value, "not supported integral type."); } } else { if constexpr (T_SIZE == 8) { overflow = WIN_EASY_OPER(ULongLongSub, ULONGLONG, a, b, c); } else if constexpr (T_SIZE == 4) { overflow = WIN_EASY_OPER(ULongSub, ULONG, a, b, c); } else if constexpr (T_SIZE == 2) { overflow = WIN_EASY_OPER(UShortSub, USHORT, a, b, c); } else if constexpr (T_SIZE == 1) { overflow = WIN_EASY_OPER(UInt8Sub, UINT8, a, b, c); } else { static_assert(std::false_type::value, "not supported integral type."); } } // Similarly, manually calculate wrapping value. if (overflow) *c = ub_signed_int_sub(a, b); return overflow; #endif } /** * @private * @brief Multiplication with overflow detection based on hardware instructions. * @param[in] a The left operand of the multiplication. * @param[in] b The right operand of the multiplication. * @param[out] c The reference to the variable storing the result. * @return Returns true if an overflow occurs, false otherwise. */ template requires std::integral bool hardware_mul_overflow(T a, T b, T* c) { if (c == nullptr) [[unlikely]] throw std::logic_error("invalid nullptr"); #if defined(YYCC_HARDWARE_OVERFLOW_GCC_FNS) return __builtin_mul_overflow(a, b, c); #else bool overflow = false; constexpr size_t T_SIZE = sizeof(T); if constexpr (std::is_signed_v) { if constexpr (T_SIZE == 8) { overflow = WIN_EASY_OPER(LongLongMult, LONGLONG, a, b, c); } else if constexpr (T_SIZE == 4) { overflow = WIN_EASY_OPER(LongMult, LONG, a, b, c); } else if constexpr (T_SIZE == 2) { overflow = WIN_EASY_OPER(ShortMult, SHORT, a, b, c); } else if constexpr (T_SIZE == 1) { overflow = WIN_EASY_OPER(Int8Mult, INT8, a, b, c); } else { static_assert(std::false_type::value, "not supported integral type."); } } else { if constexpr (T_SIZE == 8) { overflow = WIN_EASY_OPER(ULongLongMult, ULONGLONG, a, b, c); } else if constexpr (T_SIZE == 4) { overflow = WIN_EASY_OPER(ULongMult, ULONG, a, b, c); } else if constexpr (T_SIZE == 2) { overflow = WIN_EASY_OPER(UShortMult, USHORT, a, b, c); } else if constexpr (T_SIZE == 1) { overflow = WIN_EASY_OPER(UInt8Mult, UINT8, a, b, c); } else { static_assert(std::false_type::value, "not supported integral type."); } } // Similarly, manually calculate wrapping value. if (overflow) *c = ub_signed_int_mul(a, b); return overflow; #endif } // YYC MARK: // Delete the defined macro to prevent polluting the content later. #if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS) #undef WIN_EASY_OPER #endif #pragma endregion #pragma region Wrapping operations // YYC MARK: // wrapping_* function family will wrap the result in any scenario. /** * @brief Performs a wrapping 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 wrapping result of the addition operation. */ template requires std::integral T wrapping_add(T a, T b) { return ub_signed_int_add(a, b); } /** * @brief Performs a wrapping 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 wrapping result of the subtraction operation. */ template requires std::integral T wrapping_sub(T a, T b) { return ub_signed_int_sub(a, b); } /** * @brief Performs a wrapping 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 wrapping result of the multiplication operation. */ template requires std::integral T wrapping_mul(T a, T b) { return ub_signed_int_mul(a, b); } /** * @brief Performs a wrapping 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 wrapping result of the division operation. * @exception std::logic_error If division by zero occurs. */ template requires std::integral T wrapping_div(T a, T b) { // Division by zero is undefined behavior. if (ub_div_zero(a, b)) throw std::logic_error("div with 0"); // `INT_MIN / -1` overflow undefined behavior. if (ub_signed_int_min_div_minus_one(a, b)) // "a" self is the minimum value of signed integer, return it directly. // There is no need to re-fetch it by std::numeric_limits. return a; return a / b; } #pragma endregion #pragma region Checked operations // YYC MARK: // If overflow occurs when using checked_* function family, // these functions will return std::nullopt, otherwise the computed result. /** * @brief Performs a checked 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 An std::optional containing the result if no overflow occurs, otherwise std::nullopt. */ template requires std::integral std::optional checked_add(T a, T b) { T result; if (hardware_add_overflow(a, b, &result)) return std::nullopt; return result; } /** * @brief Performs a checked 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 An std::optional containing the result if no overflow occurs, otherwise std::nullopt. */ template requires std::integral std::optional checked_sub(T a, T b) { T result; if (hardware_sub_overflow(a, b, &result)) return std::nullopt; return result; } /** * @brief Performs a checked 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 An std::optional containing the result if no overflow occurs, otherwise std::nullopt. */ template requires std::integral std::optional checked_mul(T a, T b) { T result; if (hardware_mul_overflow(a, b, &result)) return std::nullopt; return result; } /** * @brief Performs a checked 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 * An std::optional containing the result, * if no undefined behavior (division by zero or overflow) occurs, * otherwise std::nullopt. */ template requires std::integral std::optional checked_div(T a, T b) { // Division by zero is undefined behavior. if (ub_div_zero(a, b)) return std::nullopt; // `INT_MIN / -1` overflow undefined behavior. if (ub_signed_int_min_div_minus_one(a, b)) return std::nullopt; return a / b; } #pragma endregion #pragma region Overflowing operations // YYC MARK: // overflowing_* function family return a tuple with 2 items as the result. // First is the wrapping value and second is a boolean indicates whether overflow occurs. /** * @brief The result returned by the overflow function family. * @details * The first item is the operation result. * The second item indicating whether an overflow has occurred. True means overflow, otherwise false. */ template requires std::integral using OverflowingPair = std::pair; /** * @brief Performs an addition operation with overflow detection. * @tparam T Integer type. * @param[in] a The left operand of the addition. * @param[in] b The right operand of the addition. * @return A pair holding result and overflow flag. */ template requires std::integral OverflowingPair overflowing_add(T a, T b) { T result; bool overflow = hardware_add_overflow(a, b, &result); return std::make_pair(result, overflow); } /** * @brief Performs a subtraction operation with overflow detection. * @tparam T Integer type. * @param[in] a The left operand of the subtraction. * @param[in] b The right operand of the subtraction. * @return A pair holding result and overflow flag. */ template requires std::integral OverflowingPair overflowing_sub(T a, T b) { T result; bool overflow = hardware_sub_overflow(a, b, &result); return std::make_pair(result, overflow); } /** * @brief Performs a multiplication operation with overflow detection. * @tparam T Integer type. * @param[in] a The left operand of the multiplication. * @param[in] b The right operand of the multiplication. * @return A pair holding result and overflow flag. */ template requires std::integral OverflowingPair overflowing_mul(T a, T b) { T result; bool overflow = hardware_mul_overflow(a, b, &result); return std::make_pair(result, overflow); } /** * @brief Performs a division operation with overflow detection. * @tparam T Integer type. * @param[in] a The left operand of the division. * @param[in] b The right operand of the division. * @return A pair holding result and overflow flag. * @exception std::logic_error If division by zero occurs. */ template requires std::integral OverflowingPair overflowing_div(T a, T b) { // Division by zero is undefined behavior. if (ub_div_zero(a, b)) throw std::logic_error("div with 0"); // `INT_MIN / -1` overflow undefined behavior. if (ub_signed_int_min_div_minus_one(a, b)) { // "a" self is minimum value, no need to get it again. return std::make_pair(a, true); } else { return std::make_pair(a / b, false); } } #pragma endregion #pragma region Saturating operations /* YYC MARK: The direction of saturation is pretty simple for unsigned integer. However, it is slightly complex for signed integer. In detail, it follow following rules: Acknowledge the truth for signed integer: abs(MIN) = abs(MAX) + 1 - ADD operation: - Range operation rule: [a, b] + [c, d] = [a+c, b+d] - Pos+Pos -> [0, MAX] + [0, MAX] -> [0, 2 * MAX]. May overflow -> Saturating to MAX. - Neg+Neg -> [MIN, -1] + [MIN, -1] -> [2 * MIN, -2]. May underflow -> Saturating to MIN. - Pos+Neg -> [0, MAX] + [MIN, -1] -> [MIN, MAX - 1]. Impossible overflow or underflow. - SUB Operation: - Range operation rule: [a, b] - [c, d] = [a-d, b-c] - Pos-Neg -> [0, MAX] - [MIN, -1] -> [1, MAX - MIN]. Maybe overflow -> Saturating to MAX. - Neg-Pos -> [MIN, -1] - [0, MAX] -> [MIN - MAX, -1]. Maybe underflow -> Saturating to MIN. - Pos-Pos -> [0, MAX] - [0, MAX] -> [-MAX, MAX]. Impossible overflow or underflow. - Neg-Neg -> [MIN, -1] - [MIN, -1] -> [MIN + 1, -(MIN + 1)]. Impossible overflow or underflow. - MUL Operation: - Pos*Pos -> Maybe overflow -> Saturating to MAX. - Pos*Neg -> Maybe underflow -> Saturating to MIN. - Neg*Neg -> Maybe overflow -> Saturating to MAX. */ /** * @brief Performs a saturating addition operation on two integers. * @details * If an overflow occurs during the addition, * the result will be saturated to the maximum/minimum value, * according to the the direction of overflow (overflow or underflow). * @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 of the saturating addition operation. */ template requires std::integral T saturating_add(T a, T b) { T result; if (hardware_add_overflow(a, b, &result)) { using Limits = std::numeric_limits; if constexpr (std::is_unsigned_v) { return Limits::max(); } else { // Overflow only occurs when 2 operand have same sign. // So we simply check the sign of one of them. return (a > 0) ? Limits::max() : Limits::min(); } } return result; } /** * @brief Performs a saturating subtraction operation on two integers. * @details * If an overflow occurs during the subtraction, * the result will be saturated to the maximum/minimum value, * according to the the direction of overflow (overflow or underflow). * @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 of the saturating subtraction operation. */ template requires std::integral T saturating_sub(T a, T b) { T result; if (hardware_sub_overflow(a, b, &result)) { using Limits = std::numeric_limits; if constexpr (std::is_unsigned_v) { return 0; } else { // Overflow only occurs when 2 operand have different sign. // So we simply compare these 2 operand. // If a < b, then "a" is negative, otherwise positive. return (a < b) ? Limits::min() : Limits::max(); } } return result; } /** * @brief Performs a saturating multiplication operation on two integers. * @details * If an overflow occurs during the multiplication, * the result will be saturated to the maximum/minimum value, * according to the the direction of overflow (overflow or underflow). * @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 of the saturating multiplication operation. */ template requires std::integral T saturating_mul(T a, T b) { T result; if (hardware_mul_overflow(a, b, &result)) { using Limits = std::numeric_limits; if constexpr (std::is_unsigned_v) { return Limits::max(); } else { // Check whether 2 operands have different sign. // If the result of XOR is true, these 2 operands should have different sign. return ((a ^ b) < 0) ? Limits::min() : Limits::max(); } } return result; } /** * @brief Performs a saturating division operation on two integers. * @details * If an overflow occurs during the division, * the result will be saturated to the maximum/minimum value, * according to the the direction of overflow (overflow or underflow). * @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 of the saturating division operation. * @exception std::logic_error If division by zero occurs. */ template requires std::integral T saturating_div(T a, T b) { // Division by zero is undefined behavior. if (ub_div_zero(a, b)) throw std::logic_error("div with zero"); // `INT_MIN / -1` overflow undefined behavior. if (ub_signed_int_min_div_minus_one(a, b)) { return std::numeric_limits::max(); } else { return a / b; } } #pragma endregion #pragma region Ordinary operations // YYC MARK: // Rust-way add, sub, mul and div operators. // There is no any undefined behavior which may occurs in these functions. // These normal operator is just an alias to wrapping_* function family. /** * @brief Performs an 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 of the addition operation. */ template requires std::integral T add(T a, T b) { return wrapping_add(a, b); } /** * @brief Performs a 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 of the subtraction operation. */ template requires std::integral T sub(T a, T b) { return wrapping_sub(a, b); } /** * @brief Performs a 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 of the multiplication operation. */ template requires std::integral T mul(T a, T b) { return wrapping_mul(a, b); } /** * @brief Performs a 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 of the division operation. * @exception std::logic_error If division by zero or value overflow occurs. */ template requires std::integral T div(T a, T b) { return wrapping_div(a, b); } #pragma endregion } // namespace yycc::num::safe_op // YYC MARK: // Delete the macro definition to prevent polluting the content later. #undef YYCC_HARDWARE_OVERFLOW_GCC_FNS #undef YYCC_HARDWARE_OVERFLOW_WIN32_FNS