From 2dacb19933515f15599487aec67890b7bb2d5fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20=C4=8Cerm=C3=A1k?= Date: Tue, 21 Aug 2018 23:07:03 +0200 Subject: [PATCH] [safe_op] Add Safe::abs(), calculates absoulte value without UB Add a drop-in replacement for std::abs which never produces negative values and thereby never invokes undefined behavior. --- src/safe_op.hpp | 31 +++++++++++++++++++++++++++++++ unitTests/test_safe_op.cpp | 9 +++++++++ 2 files changed, 40 insertions(+) diff --git a/src/safe_op.hpp b/src/safe_op.hpp index fefdd6c0..74548d8e 100644 --- a/src/safe_op.hpp +++ b/src/safe_op.hpp @@ -302,6 +302,37 @@ namespace Safe return res; } + /*! + * @brief Calculates the absolute value of a number without producing + * negative values. + * + * The "standard" implementation of `abs(num)` (`num < 0 ? -num : num`) + * produces negative values when `num` is the smallest negative number. This + * is caused by `-1 * INTMAX = INTMIN + 1`, i.e. the real result of + * `abs(INTMIN)` overflows the integer type and results in `INTMIN` again + * (this is not guaranteed as it invokes undefined behavior). + * + * This function does not exhibit this behavior, it returns + * `std::numeric_limits::max()` when the input is + * `std::numeric_limits::min()`. The downside of this is that two + * negative values produce the same absolute value: + * `std::numeric_limits::min()` and `std::numeric_limits::min() + 1`. + * + * @tparam T a signed integer type + * @param[in] num The number which absolute value should be computed. + * @throws Never throws an exception. + * @return The absolute value of `num` or `std::numeric_limits::max()` + * when `num == std::numeric_limits::min()`. + */ + template + typename Internal::enable_if::VALUE, T>::type abs(T num) throw() + { + if (num == std::numeric_limits::min()) { + return std::numeric_limits::max(); + } + return num < 0 ? -num : num; + } + } // namespace Safe #endif // SAFE_OP_HPP_ diff --git a/unitTests/test_safe_op.cpp b/unitTests/test_safe_op.cpp index 49ab4b39..d578e362 100644 --- a/unitTests/test_safe_op.cpp +++ b/unitTests/test_safe_op.cpp @@ -181,3 +181,12 @@ TEST(safeAdd, checkSignedOverflow) test_safe_add(); test_safe_add(); } + +TEST(safeAbs, checkValues) +{ + static const int values[] = {-1, 1, std::numeric_limits::max(), std::numeric_limits::min() + 1}; + for (size_t i = 0; i < sizeof(values) / sizeof(*values); ++i) { + ASSERT_EQ(Safe::abs(values[i]), abs(values[i])); + } + ASSERT_EQ(Safe::abs(std::numeric_limits::min()), std::numeric_limits::max()); +}