From fcac886f07a14ea9bfa76cd67fc9820c32f66ce5 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 5 Aug 2025 13:53:59 +0800 Subject: [PATCH] refactor: migrate rust-like ops. - migrate rust-like ops. - migrate testbench for them but not finished. --- src/CMakeLists.txt | 3 + src/yycc/num/op.hpp | 35 ++ src/yycc/num/safe_cast.hpp | 155 +++++++++ src/yycc/num/safe_op.hpp | 572 +++++++++++++++++++++++++++++++ testbench/CMakeLists.txt | 3 + testbench/yycc/num/op.cpp | 13 + testbench/yycc/num/safe_cast.cpp | 9 + testbench/yycc/num/safe_op.cpp | 300 ++++++++++++++++ 8 files changed, 1090 insertions(+) create mode 100644 src/yycc/num/op.hpp create mode 100644 src/yycc/num/safe_cast.hpp create mode 100644 src/yycc/num/safe_op.hpp create mode 100644 testbench/yycc/num/op.cpp create mode 100644 testbench/yycc/num/safe_cast.cpp create mode 100644 testbench/yycc/num/safe_op.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b6a8949..79fa1e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,9 @@ FILES yycc/patch/fopen.hpp yycc/num/parse.hpp yycc/num/stringify.hpp + yycc/num/safe_cast.hpp + yycc/num/safe_op.hpp + yycc/num/op.hpp yycc/rust/prelude.hpp yycc/rust/primitive.hpp yycc/rust/panic.hpp diff --git a/src/yycc/num/op.hpp b/src/yycc/num/op.hpp new file mode 100644 index 0000000..4d5f1d2 --- /dev/null +++ b/src/yycc/num/op.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +/** + * @brief The namespace providing function relative robust numeric operations. + * @details + * 在使用Rust编写一些程序后,深刻地认识到Rust中针对基本类型的运算符的丰富程度。 + * 提供了诸如整数向上取整除法等便利操作。 + * 因此我在这个命名空间里复刻Rust中的这些便利功能。 + * + * 由于没有需求,目前暂未实现以下功能: + * \li 仅支持无符号整数向上取整除法。 + * \li 显式指定运算溢出行为 + */ +namespace yycc::num::op { + + /** + * @brief 无符号整数的向上整除 + * @details + * 执行两个无符号整数之间的整除,并将结果向上取整。 + * 该函数可能在很多加密函数中进行使用以分配合适空间。 + * @tparam T 除法操作建立在的无符号整数类型 + * @param[in] lhs 左操作数 + * @param[in] rhs 右操作数 + * @return 向上取整的整除结果 + */ + template + requires std::unsigned_integral + T div_ceil(T lhs, T rhs) { + if (rhs == 0) throw std::logic_error("div with 0"); + return (lhs % rhs == 0) ? (lhs / rhs) : (lhs / rhs) + 1u; + } + +} diff --git a/src/yycc/num/safe_cast.hpp b/src/yycc/num/safe_cast.hpp new file mode 100644 index 0000000..85fc44d --- /dev/null +++ b/src/yycc/num/safe_cast.hpp @@ -0,0 +1,155 @@ +#pragma once +#include +#include +#include +#include + +/** + * @brief The namespace providing functions which safely cast numeric value from one type to another. + * @details + * 在编写Rust的时候,深刻地认识到,在范围不同的类型之间进行转换是一件非常重要且容易出错的事情。 + * 对于拓宽转换,我们可以安全地,不加考虑地直接进行转换。 + * 对于收窄转换,我们则需要引入Result机制,来判断是否出错。 + * 这些功能在Rust中对所有基础类型都有实现,且通过From trait来进行统一管理,非常完美。 + * 但在C++中,我们需要手动重现它们。 + * + * 在这个命名空间中,我们将转换函数分为了两类,一种是to,适用于肯定能安全转换的类型。 + * 另一种是try_to,用于可能存在风险的转换。 + * 但直接套用CAN_SAFE_TO来决定是否可以安全转换是不正确的。 + * Rust中,这些转换安全与否是通过trait的不同(From和TryFrom)来手动编写的。 + * 而在这里我们粗暴地用编译时数据类型大小的比较结果来决定是否安全转换。 + * 这就导致那些变长基本数据类型在不同编译平台上致使CAN_SAFE_TO输出不同结果, + * 从而影响我们的代码的可移植性。 + * 例如在uint64_t和size_t之间的变换,在64位平台上我们可以使用to函数直接转换, + * 然而在32位平台上,就会出现编译报错,造成了代码的可移植性问题。 + * Rust中的解决方案是Rust规定了Usize的最小大小(32位),只有小于其最小大小的数据才能安全的转换(例如u16)。 + * 但在C++中我们不能那么做,也无从做起,因为我们不能知道每一个变长基本数据类型的最小大小。 + * 所以我们使用另一种方案来解决这个问题。 + * + * 我们的解决方案是,为to函数强制应用CAN_SAFE_TO规则,而不对try_to函数强制应用。 + * 在try_to函数内部,我们再使用CAN_SAFE_TO,判断是否可以安全转换,如果可以,就直接转换,否则做一系列判定。 + * 这样以来,程序员就需要手动判定两个数据类型之间是否肯定可以安全转换,然后再使用to函数。 + * 但至少,编译器会在你误使用to函数的时候,及时地给你抛出错误,这时候只要改用try_to就好了。 + * 同时,如果全篇使用try_to,也不会因为做了无用检查而影响性能,因为无用检查已经通过if constexpr删除了。 + * + * 由于没有需求,目前不支持以下转换: + * \li 浮点数与浮点数之间的转换。 + * \li 浮点数与整数之间的转换。 + */ +namespace yycc::num::safe_cast { + + /// @brief All possible error raised in this module. + enum class CastError { + Overflow, ///< 转换时发生向上溢出错误。 + Underflow, ///< 转换时发生向下溢出错误。 + }; + + /// @brief The result type in this module. + template + using Result = std::expected; + + /** + * @private + * @brief 检查一个整数类型是否可以安全地转换为另一个整数类型 + * @return + */ + template + requires std::integral && std::integral + constexpr bool can_safe_to() { + // 检查 TSrc 和 TDst 是否有符号 + constexpr bool is_src_signed = std::is_signed_v; + constexpr bool is_dst_signed = std::is_signed_v; + + // 获取 TSrc 和 TDst 的范围 + constexpr TSrc src_min = std::numeric_limits::min(); + constexpr TSrc src_max = std::numeric_limits::max(); + constexpr TDst dst_min = std::numeric_limits::min(); + constexpr TDst dst_max = std::numeric_limits::max(); + + if constexpr (is_src_signed) { + if constexpr (is_dst_signed) { + // 有符号向有符号转换,两端都需要检查。 + // 如果完全处于范围内,则肯定可以安全转换。 + return dst_min <= src_min && dst_max >= src_max; + } else { + // 有符号向无符号转换,总是不安全的。 + // 因为会存在负数情况 + return false; + } + } else { + if constexpr (is_dst_signed) { + // 无符号向有符号转换,则只检查上端。 + // 如果上端够小内,则肯定可以安全转换。 + return dst_max >= src_max; + } else { + // 无符号向无符号转换,则只检查上端,因为下端均为0。 + return dst_max >= src_max; + } + } + } + + /** + * @private + * @brief can_safe_to()的变量版本 + * @details 为后文约束提供的便利变量 + */ + template + requires std::integral && std::integral + inline constexpr bool CAN_SAFE_TO = can_safe_to(); + + /** + * @brief 将一个整数类型转换为另一个整数类型。 + * @details 类似于Rust中的From<> trait,只不过颠倒了一下顺序(从from变为to)。 + * @return 转换后的结果。 + */ + template + requires std::integral && std::integral && CAN_SAFE_TO + TDst to(const TSrc& lhs) { + return static_cast(lhs); + } + + /** + * @brief 尝试将一个整数类型转换为另一个整数类型。 + * @details 类似于Rust中的TryFrom<> trait,只不过颠倒了一下顺序(从from变为to)。 + * @return 一个包含转换结果的Result类型。 + */ + template + requires std::integral && std::integral + Result try_to(const TSrc& lhs) { + // 检查是否可以直接转换 + if constexpr (CAN_SAFE_TO) { + return static_cast(lhs); + } else { + // 检查 TSrc 和 TDst 是否有符号 + constexpr bool is_src_signed = std::is_signed_v; + constexpr bool is_dst_signed = std::is_signed_v; + + // 获取 TSrc 和 TDst 的范围 + constexpr TSrc src_min = std::numeric_limits::min(); + constexpr TSrc src_max = std::numeric_limits::max(); + constexpr TDst dst_min = std::numeric_limits::min(); + constexpr TDst dst_max = std::numeric_limits::max(); + + // 检查是否可以安全转换 + if constexpr (is_src_signed == is_dst_signed) { + // 如果两者都是有符号或无符号,直接比较范围 + if (lhs < dst_min) return std::unexpected(CastError::Underflow); + if (lhs > dst_max) return std::unexpected(CastError::Overflow); + return static_cast(lhs); + } else { + // 两者符号不一致 + if constexpr (is_src_signed) { + // 如果 TSrc 是有符号,TDst 是无符号,需要确保 lhs 不小于 0 + if (lhs < 0) return std::unexpected(CastError::Underflow); + if (lhs > dst_max) return std::unexpected(CastError::Overflow); + return static_cast(lhs); + } else { + // 如果 TSrc 是无符号,TDst 是有符号,需要确保 lhs 不大于 TDst 的最大值 + if (lhs > dst_max) return std::unexpected(CastError::Overflow); + return static_cast(lhs); + } + } + } + } + +} // namespace yycc::num::safe_cast diff --git a/src/yycc/num/safe_op.hpp b/src/yycc/num/safe_op.hpp new file mode 100644 index 0000000..2c0ff94 --- /dev/null +++ b/src/yycc/num/safe_op.hpp @@ -0,0 +1,572 @@ +#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) +#include "../windows/import_guard_head.hpp" +#include +#include "../windows/import_guard_tail.hpp" +#endif + +/** + * @brief The namespace providing Rust-like safe arithmetic operations. + */ +namespace yycc::num::safe_op { + + // YYC MARK: + // 在使用Rust编写一些程序后,深刻地认识到Rust中针对基本类型的运算符的丰富程度。 + // 可以显式地指定运算溢出的行为(wrapping,checked,overflowing,saturating四选一) + // 因此我在这个命名空间里复刻Rust中的这些便利功能。 + + // YYC MARK: + // 实现说明: + // * Wrapping 运算: + // - 无符号整数直接使用默认溢出行为。 + // - 有符号整数通过无符号类型转换模拟溢出回绕。 + // * Checked 运算: + // - 使用编译器内置函数检测溢出,返回std::optional。 + // - 除零或溢出时返回std::nullopt。 + // * Overflowing 运算: + // - 返回包含结果和溢出标志的结构体OverflowResult。 + // - 显式处理除零和有符号最小值除以-1的情况。 + // * Saturating 运算: + // - 溢出时返回类型最大值或最小值。 + // - 根据操作数符号判断饱和方向。 + +#pragma region Undefined Behaviors + + // YYC MARK: + // 需要注意以下未定义行为: + // * 有符号整数运算结果超出该类型表示范围时(如 INT_MAX + 1),行为未定义。 + // * 对 INT_MIN / -1 的除法可能溢出,导致UB。 + // * 无符号整数溢出时行为是明确定义的(按模 2^N 回绕),但需注意逻辑错误。 + // * 除数为零时行为未定义。 + + /** + * @private + * @brief 两数相加,同时考虑有符号整数溢出的未定义行为 + * @param a 加法的左操作数 + * @param b 加法的右操作数 + * @return 考虑了有符号整数未定义行为的加法结果。 + */ + template + requires std::integral + T ub_signed_int_add(T a, T b) { + if constexpr (std::is_unsigned_v) { + // 无符号数的加减乘都是自然回绕的,可以直接使用运算符。 + return a + b; + } else { + // 有符号数的溢出在C++中是未定义的。 + // 所以需要使用位操作强制转换为无符号进行计算,然后再转回来。 + using UT = std::make_unsigned_t; + return static_cast(static_cast(a) + static_cast(b)); + } + } + + /** + * @private + * @brief 两数相减,同时考虑有符号整数溢出的未定义行为 + * @param a 减法的左操作数 + * @param b 减法的右操作数 + * @return 考虑了有符号整数未定义行为的减法结果。 + */ + 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 两数相乘,同时考虑有符号整数溢出的未定义行为 + * @param a 乘法的左操作数 + * @param b 乘法的右操作数 + * @return 考虑了有符号整数未定义行为的乘法结果。 + */ + 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 检查有符号最小值除以-1时的未定义行为 + * @param a 除法的左操作数 + * @param b 除法的右操作数 + * @return 如果会发生未定义行为,则为true,否则为false + */ + template + requires std::integral + bool ub_signed_int_min_div_minus_one(T a, T b) { + if constexpr (std::is_signed_v) { + // 对有符号数来说, INT_MIN / -1 的除法可能溢出 + // (如 INT_MIN == -INT_MIN - 1 时),导致未定义行为。 + if (b == -1 && a == std::numeric_limits::min()) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + /** + * @private + * @brief 检查除以0的未定义行为 + * @details 如果发生除以0,则引发panic + * @param a 除法的左操作数 + * @param b 除法的右操作数 + * @return 如果会发生未定义行为,则为true,否则为false + */ + 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: +// 定义一个方便在Windows函数中计算的宏 +#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 基于硬件指令的带溢出检测的加法 + * @param[in] a 加法的左操作数 + * @param[in] b 加法的右操作数 + * @param[out] c 存放结果的引用 + * @return 如果发生溢出,则为true,否则为false。 + */ + 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."); + } + } + // 由于Windows函数限制,如果发生溢出,函数不会计算结果。 + // 所以我们要手动填写回绕了的结果 + if (overflow) *c = ub_signed_int_add(a, b); + return overflow; +#endif + } + + /** + * @private + * @brief 基于硬件指令的带溢出检测的减法 + * @param[in] a 减法的左操作数 + * @param[in] b 减法的右操作数 + * @param[out] c 存放结果的引用 + * @return 如果发生溢出,则为true,否则为false。 + */ + 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."); + } + } + // 同理,手算溢出值 + if (overflow) *c = ub_signed_int_sub(a, b); + return overflow; +#endif + } + + /** + * @private + * @brief 基于硬件指令的带溢出检测的乘法 + * @param[in] a 乘法的左操作数 + * @param[in] b 乘法的右操作数 + * @param[out] c 存放结果的引用 + * @return 如果发生溢出,则为true,否则为false。 + */ + 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."); + } + } + // 同理,手算溢出值 + if (overflow) *c = ub_signed_int_mul(a, b); + return overflow; +#endif + } + +// YYC MARK: 删除定义的宏,防止污染 +#if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS) +#undef WIN_EASY_OPER +#endif + +#pragma endregion + +#pragma region Wrapping operations + + // YYC MARK: + // 使用 wrapping_* 方法在所有模式下进行包裹。 + + template + requires std::integral + T wrapping_add(T a, T b) { + return ub_signed_int_add(a, b); + } + + template + requires std::integral + T wrapping_sub(T a, T b) { + return ub_signed_int_sub(a, b); + } + + template + requires std::integral + T wrapping_mul(T a, T b) { + return ub_signed_int_mul(a, b); + } + + template + requires std::integral + T wrapping_div(T a, T b) { + // 除以0是未定义行为 + if (ub_div_zero(a, b)) throw std::logic_error("div with 0"); + // 对有符号数来说, INT_MIN / -1 的除法可能溢出 + // (如 INT_MIN == -INT_MIN - 1 时),导致未定义行为。 + if (ub_signed_int_min_div_minus_one(a, b)) + // 此时a就是有符号整数最小值,直接返回他即可。 + // 不需要再通过std::numeric_limits去访问最小值。 + return a; + return a / b; + } + +#pragma endregion + +#pragma region Checked operations + + // YYC MARK: 使用 checked_* 方法时发生溢出,则返回 None 值 + + 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; + } + + 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; + } + + 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; + } + + template + requires std::integral + std::optional checked_div(T a, T b) { + // 除以0返回空值 + if (ub_div_zero(a, b)) return std::nullopt; + // 溢出返回空值 + 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_* 方法返回该值和一个指示是否存在溢出的布尔值 + + /** + * @brief Overflow系列运算函数返回的结果 + * @details + * 第一项为运算结果。 + * 第二项为指示是否发生了溢出,true为溢出,否则为false。 + */ + template + requires std::integral + using OverflowingPair = std::pair; + + 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); + } + + 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); + } + + 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); + } + + template + requires std::integral + OverflowingPair overflowing_div(T a, T b) { + // 除以0需要panic + if (ub_div_zero(a, b)) throw std::logic_error("div with 0"); + // 溢出只可能发生在有符号最小值除以-1上 + if (ub_signed_int_min_div_minus_one(a, b)) { + // a就是最小值,无需再次获取 + return std::make_pair(a, true); + } else { + return std::make_pair(a / b, false); + } + } + +#pragma endregion + +#pragma region Saturating operations + + // YYC MARK: 使用 saturating_* 方法使值达到最小值或最大值 + // 无符号的溢出判定较为简单,有符号的则稍显复杂,具体遵守如下规则 + // 认识到对于有符号:abs(MIN) = abs(MAX) + 1 + // * 加法: + // - 区间运算[a, b] + [c, d] = [a+c, b+d] + // - 正+正 -> [0, MAX] + [0, MAX] -> [0, 2 * MAX],可能上溢 -> 饱和到max + // - 负+负 -> [MIN, -1] + [MIN, -1] -> [2 * MIN, -2],可能下溢 -> 饱和到min + // - 正+负 -> [0, MAX] + [MIN, -1] -> [MIN, MAX - 1],不可能溢出 + // * 减法: + // - 区间运算[a, b] - [c, d] = [a-d, b-c] + // - 正-负 -> [0, MAX] - [MIN, -1] -> [1, MAX - MIN],可能上溢 -> 饱和到max + // - 负-正 -> [MIN, -1] - [0, MAX] -> [MIN - MAX, -1],可能下溢 -> 饱和到min + // - 正-正 -> [0, MAX] - [0, MAX] -> [-MAX, MAX],不可能溢出 + // - 负-负 -> [MIN, -1] - [MIN, -1] -> [MIN + 1, -(MIN + 1)],不可能溢出 + // * 乘法: + // - 正*正 -> 可能上溢 -> 饱和到max + // - 正*负 -> 可能下溢 -> 饱和到min + // - 负*负 -> 可能上溢 -> 饱和到max + + 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 { + // 溢出只可能发生在同号情况,因此只判定其中一个操作数的符号即可。 + return (a > 0) ? Limits::max() : Limits::min(); + } + } + return result; + } + + 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 { + // 溢出只可能发生在异号情况,因此只判定两个操作数的大小即可。 + // a < b,则a为负,否则a为正 + return (a < b) ? Limits::min() : Limits::max(); + } + } + return result; + } + + 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 { + // 做异号判定,如果XOR为true,则为异号 + return ((a ^ b) < 0) ? Limits::min() : Limits::max(); + } + } + return result; + } + + template + requires std::integral + T saturating_div(T a, T b) { + // 除以0需要panic + if (ub_div_zero(a, b)) throw std::logic_error("div with zero"); + // 如果发生最小值除以0的情况,那么溢出到最大值 + 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的方式进行四则运算,不存在未定义行为。 + // 默认的四则运算与wrapping_*系列函数行为一致。 + + template + requires std::integral + T add(T a, T b) { + return wrapping_add(a, b); + } + + template + requires std::integral + T sub(T a, T b) { + return wrapping_sub(a, b); + } + + template + requires std::integral + T mul(T a, T b) { + return wrapping_mul(a, b); + } + + template + requires std::integral + T div(T a, T b) { + return wrapping_div(a, b); + } + +#pragma endregion + +} // namespace yycc::num::safe_op + +// YYC MARK: 删除宏定义,防止污染后面的内容 +#undef YYCC_HARDWARE_OVERFLOW_GCC_FNS +#undef YYCC_HARDWARE_OVERFLOW_WIN32_FNS diff --git a/testbench/CMakeLists.txt b/testbench/CMakeLists.txt index 06cb9ec..b5327b1 100644 --- a/testbench/CMakeLists.txt +++ b/testbench/CMakeLists.txt @@ -13,6 +13,9 @@ PRIVATE yycc/string/reinterpret.cpp yycc/num/parse.cpp yycc/num/stringify.cpp + yycc/num/op.cpp + yycc/num/safe_cast.cpp + yycc/num/safe_op.cpp ) target_sources(YYCCTestbench PRIVATE diff --git a/testbench/yycc/num/op.cpp b/testbench/yycc/num/op.cpp new file mode 100644 index 0000000..8e71056 --- /dev/null +++ b/testbench/yycc/num/op.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +#define OP ::yycc::num::op + +namespace yycctest::num::op { + + TEST(NumOp, DivCeil) { + EXPECT_EQ(OP::div_ceil(7, 4), UINT32_C(2)); + } + +} diff --git a/testbench/yycc/num/safe_cast.cpp b/testbench/yycc/num/safe_cast.cpp new file mode 100644 index 0000000..3b18066 --- /dev/null +++ b/testbench/yycc/num/safe_cast.cpp @@ -0,0 +1,9 @@ +#include +#include +#include + +namespace yycctest::num::safe_cast { + + // TODO: Add testbench + +} diff --git a/testbench/yycc/num/safe_op.cpp b/testbench/yycc/num/safe_op.cpp new file mode 100644 index 0000000..1f11f50 --- /dev/null +++ b/testbench/yycc/num/safe_op.cpp @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include + +#include + +#define OP ::yycc::num::safe_op + +namespace yycctest::num::safe_op { + + template + constexpr T MAX = std::numeric_limits::max(); + + template + constexpr T MIN = std::numeric_limits::min(); + +#pragma region Wrapping operations + + TEST(NumSafeOp, WrappingAdd) { + // Unsigned + EXPECT_EQ(OP::wrapping_add(200, 55), UINT32_C(255)); + EXPECT_EQ(OP::wrapping_add(200, MAX), UINT32_C(199)); + // Signed + EXPECT_EQ(OP::wrapping_add(100, 27), INT32_C(127)); + EXPECT_EQ(OP::wrapping_add(MAX, 2), MIN + 1); + } + + TEST(NumSafeOp, WrappingSub) { + // Unsigned + EXPECT_EQ(OP::wrapping_sub(100, 100), 0); + EXPECT_EQ(OP::wrapping_sub(100, MAX), UINT32_C(101)); + // Signed + EXPECT_EQ(OP::wrapping_sub(0, 127), INT32_C(-127)); + EXPECT_EQ(OP::wrapping_sub(-2, MAX), MAX); + } + + TEST(NumSafeOp, WrappingMul) { + // Unsigned + EXPECT_EQ(OP::wrapping_mul(10, 12), UINT8_C(120)); + EXPECT_EQ(OP::wrapping_mul(25, 12), UINT8_C(44)); + // Signed + EXPECT_EQ(OP::wrapping_mul(10, 12), INT32_C(120)); + EXPECT_EQ(OP::wrapping_mul(11, 12), INT32_C(-124)); + } + + TEST(NumSafeOp, WrappingDiv) { + // Unsigned + EXPECT_EQ(OP::wrapping_div(100, 10), UINT32_C(10)); + // Signed + EXPECT_EQ(OP::wrapping_div(100, 10), INT32_C(10)); + EXPECT_EQ(OP::wrapping_div(-128, -1), INT32_C(-128)); + } + +#pragma endregion + +#pragma region Checked operations + + TEST(NumSafeOp, CheckedAdd) { + // Unsigned + { + auto rv = OP::checked_add(MAX - 2, 1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), MAX - 1); + } + { + auto rv = OP::checked_add(MAX - 2, 3); + EXPECT_FALSE(rv.has_value()); + } + // Signed + { + auto rv = OP::checked_add(MAX - 2, 1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), MAX - 1); + } + { + auto rv = OP::checked_add(MAX - 2, 3); + EXPECT_FALSE(rv.has_value()); + } + } + + TEST(NumSafeOp, CheckedSub) { + // Unsigned + { + auto rv = OP::checked_sub(1, 1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), 0); + } + { + auto rv = OP::checked_sub(0, 1); + EXPECT_FALSE(rv.has_value()); + } + // Signed + { + auto rv = OP::checked_sub(MIN + 2, 1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), MIN + 1); + } + { + auto rv = OP::checked_sub(MIN + 2, 3); + EXPECT_FALSE(rv.has_value()); + } + } + + TEST(NumSafeOp, CheckedMul) { + // Unsigned + { + auto rv = OP::checked_mul(5, 1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), 5); + } + { + auto rv = OP::checked_mul(MAX, 2); + EXPECT_FALSE(rv.has_value()); + } + // Signed + { + auto rv = OP::checked_mul(MAX, 1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), MAX); + } + { + auto rv = OP::checked_mul(MAX, 2); + EXPECT_FALSE(rv.has_value()); + } + } + + TEST(NumSafeOp, CheckedDiv) { + // Unsigned + { + auto rv = OP::checked_div(128, 2); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), 64); + } + { + auto rv = OP::checked_div(1, 0); + EXPECT_FALSE(rv.has_value()); + } + // Signed + { + auto rv = OP::checked_div(MIN + 1, -1); + ASSERT_TRUE(rv.has_value()); + EXPECT_EQ(rv.value(), INT32_C(2147483647)); + } + { + auto rv = OP::checked_div(MIN, -1); + EXPECT_FALSE(rv.has_value()); + } + { + auto rv = OP::checked_div(1, 0); + EXPECT_FALSE(rv.has_value()); + } + } + +#pragma endregion + +#pragma region Overflowing operations + + TEST(NumSafeOp, OverflowingAdd) { + // Unsigned + { + auto rv = OP::overflowing_add(5, 2); + EXPECT_EQ(rv.first, 7); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_add(MAX, 1); + EXPECT_EQ(rv.first, 0); + EXPECT_EQ(rv.second, true); + } + // Signed + { + auto rv = OP::overflowing_add(5, 2); + EXPECT_EQ(rv.first, 7); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_add(MAX, 1); + EXPECT_EQ(rv.first, MIN); + EXPECT_EQ(rv.second, true); + } + } + + TEST(NumSafeOp, OverflowingSub) { + // Unsigned + { + auto rv = OP::overflowing_sub(5, 2); + EXPECT_EQ(rv.first, 3); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_sub(0, 1); + EXPECT_EQ(rv.first, MAX); + EXPECT_EQ(rv.second, true); + } + // Signed + { + auto rv = OP::overflowing_sub(5, 2); + EXPECT_EQ(rv.first, 3); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_sub(MIN, 1); + EXPECT_EQ(rv.first, MAX); + EXPECT_EQ(rv.second, true); + } + } + + TEST(NumSafeOp, OverflowingMul) { + // Unsigned + { + auto rv = OP::overflowing_mul(5, 2); + EXPECT_EQ(rv.first, 10); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_mul(UINT32_C(1000000000), 10); + EXPECT_EQ(rv.first, UINT32_C(1410065408)); + EXPECT_EQ(rv.second, true); + } + // Signed + { + auto rv = OP::overflowing_mul(5, 2); + EXPECT_EQ(rv.first, 10); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_mul(INT32_C(1000000000), 10); + EXPECT_EQ(rv.first, INT32_C(1410065408)); + EXPECT_EQ(rv.second, true); + } + } + + TEST(NumSafeOp, OverflowingDiv) { + // Unsigned + { + auto rv = OP::overflowing_div(5, 2); + EXPECT_EQ(rv.first, 2); + EXPECT_EQ(rv.second, false); + } + // Signed + { + auto rv = OP::overflowing_div(5, 2); + EXPECT_EQ(rv.first, 2); + EXPECT_EQ(rv.second, false); + } + { + auto rv = OP::overflowing_div(MIN, -1); + EXPECT_EQ(rv.first, MIN); + EXPECT_EQ(rv.second, true); + } + } + +#pragma endregion + +#pragma region Saturating operations + + TEST(NumSafeOp, SaturatingAdd) { + // Unsigned + EXPECT_EQ(OP::saturating_add(100, 1), UINT32_C(101)); + EXPECT_EQ(OP::saturating_add(MAX, 127), MAX); + // Signed + EXPECT_EQ(OP::saturating_add(100, 1), INT32_C(101)); + EXPECT_EQ(OP::saturating_add(MAX, 100), MAX); + EXPECT_EQ(OP::saturating_add(MIN, -1), MIN); + } + + TEST(NumSafeOp, SaturatingSub) { + // Unsigned + EXPECT_EQ(OP::saturating_sub(100, 27), UINT32_C(73)); + EXPECT_EQ(OP::saturating_sub(13, 127), 0); + // Signed + EXPECT_EQ(OP::saturating_sub(100, 127), -27); + EXPECT_EQ(OP::saturating_sub(MIN, 100), MIN); + EXPECT_EQ(OP::saturating_sub(MAX, -1), MAX); + } + + TEST(NumSafeOp, SaturatingMul) { + // Unsigned + EXPECT_EQ(OP::saturating_mul(2, 10), UINT32_C(20)); + EXPECT_EQ(OP::saturating_mul(MAX, 10), MAX); + // Signed + EXPECT_EQ(OP::saturating_mul(10, 12), 120); + EXPECT_EQ(OP::saturating_mul(MAX, 10), MAX); + EXPECT_EQ(OP::saturating_mul(MIN, 10), MIN); + } + + TEST(NumSafeOp, SaturatingDiv) { + // Unsigned + EXPECT_EQ(OP::saturating_div(5, 2), UINT32_C(2)); + // Signed + EXPECT_EQ(OP::saturating_div(5, 2), 2); + EXPECT_EQ(OP::saturating_div(MAX, -1), MIN + 1); + EXPECT_EQ(OP::saturating_div(MIN, -1), MAX); + } + +#pragma endregion + +} // namespace yycctest::num::safe_op