lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [lmi] master 4ef9a222 4/5: Calculate std::abs(INT_MIN) wit


From: Greg Chicares
Subject: [lmi-commits] [lmi] master 4ef9a222 4/5: Calculate std::abs(INT_MIN) without UB
Date: Thu, 2 Jun 2022 20:13:04 -0400 (EDT)

branch: master
commit 4ef9a22264c69043f7c7228c57aafb8fcd22ad68
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>

    Calculate std::abs(INT_MIN) without UB
    
    Evidence for this comment:
    
    +/// The return type is specified explicitly because with 'auto'
    +/// gcc deduces it incorrectly in the accompanying unit test.
    
    /opt/lmi/src/lmi/math_functions_test.cpp:567:32: \
      error: conversion from ‘int’ to ‘uint16_t’ \
      {aka ‘short unsigned int’} may change value [-Werror=conversion]
      567 |         std::uint16_t u = u_abs(j);
          |                           ~~~~~^~~
---
 math_functions.hpp      | 22 +++++++++++++++++++++-
 math_functions_test.cpp | 19 +++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/math_functions.hpp b/math_functions.hpp
index dcf6cc96..a2a7c208 100644
--- a/math_functions.hpp
+++ b/math_functions.hpp
@@ -29,7 +29,7 @@
 #include <limits>
 #include <numeric>                      // midpoint(), partial_sum()
 #include <stdexcept>
-#include <type_traits>                  // /is_.*/
+#include <type_traits>                  // is_, has_, make_ ...
 #include <vector>
 
 namespace lmi
@@ -129,6 +129,26 @@ T signum(T t)
     return (0 == t) ? 0 : std::signbit(t) ? -1 : 1;
 }
 
+/// Unsigned |signed value|. Needed because std::abs(INT_MIN) is UB.
+///
+/// Asserts that both integer types have no padding, to rule out the
+///   UINT_MAX == INT_MAX == -(INT_MIN+1)
+/// case that Daniel Fischer points out somewhere on the web.
+///
+/// The return type is specified explicitly because with 'auto'
+/// gcc deduces it incorrectly in the accompanying unit test.
+
+template<typename T>
+constexpr std::make_unsigned_t<T> u_abs(T t)
+{
+    static_assert(std::is_integral_v<T>);
+    static_assert(std::is_signed_v<T>);
+    using U = std::make_unsigned_t<T>;
+    static_assert(std::has_unique_object_representations_v<T>);
+    static_assert(std::has_unique_object_representations_v<U>);
+    return (t < 0) ? -static_cast<U>(t) : static_cast<U>(t);
+}
+
 // Actuarial functions.
 //
 // Some inputs are nonsense, like interest rates less than 100%.
diff --git a/math_functions_test.cpp b/math_functions_test.cpp
index f291fd8b..08eed487 100644
--- a/math_functions_test.cpp
+++ b/math_functions_test.cpp
@@ -33,6 +33,7 @@
 #include <algorithm>                    // min()
 #include <cfloat>                       // DBL_EPSILON
 #include <cmath>                        // fabs(), isnan(), pow()
+#include <cstdint>
 #include <iomanip>
 #include <limits>
 #include <type_traits>
@@ -559,6 +560,22 @@ void test_signum(char const* file, int line)
         }
 }
 
+void test_u_abs()
+{
+    LMI_TEST_EQUAL(9223372036854775808ULL, u_abs(INT64_MIN));
+
+    LMI_TEST_EQUAL(128, u_abs(INT8_MIN));
+
+    for(std::int16_t j = INT8_MIN; j < INT8_MAX; ++j)
+        {
+        std::uint16_t u = u_abs(j);
+        if(0 <= j)
+            {LMI_TEST_EQUAL(u,  j);}
+        if(j <= 0)
+            {LMI_TEST_EQUAL(u, -j);}
+        }
+}
+
 /// Test fdlibm expm1() and log1p().
 ///
 /// Testing for exact floating-point equality seems to be a patent
@@ -781,6 +798,8 @@ int test_main(int, char*[])
     test_signum<double       >(__FILE__, __LINE__);
     test_signum<long double  >(__FILE__, __LINE__);
 
+    test_u_abs();
+
     test_expm1_log1p();
 
     sample_results();



reply via email to

[Prev in Thread] Current Thread [Next in Thread]