Skip to content

Commit 1cf32ee

Browse files
authored
Merge pull request InsightSoftwareConsortium#5797 from InsightSoftwareConsortium/constexpr-math-operations
ENH: Add constexpr overloads for `itk::Math::abs`
2 parents 121605a + 3cc3f4a commit 1cf32ee

File tree

2 files changed

+152
-47
lines changed

2 files changed

+152
-47
lines changed

Modules/Core/Common/include/itkMath.h

Lines changed: 93 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
#define itkMath_h
3030

3131
#include <cassert>
32+
#include <complex>
3233
#include <cmath>
34+
#include <type_traits>
3335
#include "itkMathDetail.h"
3436
#include "itkConceptChecking.h"
3537
#include <vnl/vnl_math.h>
@@ -823,60 +825,104 @@ using vnl_math::cube;
823825
using vnl_math::squared_magnitude;
824826

825827

826-
/*============================================
827-
Decouple dependence and exposure of vnl_math::abs operations
828-
in ITK. Placing this small amount of duplicate vnl_math
829-
code directly in ITK removes backward compatibility
830-
issues with system installed VXL versions.
831-
*/
832-
inline bool
833-
abs(bool x)
834-
{
835-
return x;
836-
}
837-
inline unsigned char
838-
abs(unsigned char x)
839-
{
840-
return x;
841-
}
842-
inline unsigned char
843-
abs(signed char x)
844-
{
845-
return x < 0 ? static_cast<unsigned char>(-x) : x;
846-
}
847-
inline unsigned char
848-
abs(char x)
849-
{
850-
return static_cast<unsigned char>(x);
851-
}
852-
inline unsigned short
853-
abs(short x)
854-
{
855-
return x < 0 ? static_cast<unsigned short>(-x) : x;
856-
}
857-
inline unsigned short
858-
abs(unsigned short x)
859-
{
860-
return x;
861-
}
862-
inline unsigned int
863-
abs(unsigned int x)
828+
/**
829+
* @brief Returns the absolute value of a number.
830+
*
831+
* This function provides a c++17 implementation of the vnl_math::abs absolute value functionality.
832+
* safe_abs preserves backward compatibility with vnl_math::abs that was originally used in ITK.
833+
*
834+
* Where std::abs returns the absolute value in the same type as the input, itk::safe_abs
835+
* returns the absolute value in the unsigned equivalent of the input type.
836+
*
837+
* @tparam T The type of the input number.
838+
* @param x The input number.
839+
* @return The absolute value of the input number as an unsigned type for integer types.
840+
*/
841+
template <typename T>
842+
constexpr auto
843+
safe_abs(T x) noexcept
864844
{
865-
return x;
845+
if constexpr (std::is_same_v<T, bool>)
846+
{
847+
// std::abs does not provide abs for bool, but ITK depends on bool support for abs.
848+
return x;
849+
}
850+
else if constexpr (std::is_integral_v<T>)
851+
{
852+
if constexpr (std::is_signed_v<T>)
853+
{
854+
// Using promotion to std::int32_t instead of std::make_unsigned_t<T>
855+
// to avoid potential overflow when T is a signed 8bit minimum (value -128)
856+
// or signed 15 bit minimum (value -32768).
857+
// Note that -(-128) is still -128 for an 8-bit signed char.
858+
if constexpr (sizeof(T) <= sizeof(int))
859+
{
860+
#if __cplusplus >= 202302L
861+
return static_cast<std::make_unsigned_t<T>>(std::abs(static_cast<int>(x)));
862+
#else
863+
return static_cast<std::make_unsigned_t<T>>((x < T(0)) ? -x : x);
864+
#endif
865+
}
866+
else if constexpr (sizeof(T) <= sizeof(long int))
867+
{
868+
#if __cplusplus >= 202302L
869+
return static_cast<std::make_unsigned_t<T>>(std::abs(static_cast<long int>(x)));
870+
#else
871+
return static_cast<std::make_unsigned_t<T>>((x < T(0)) ? -x : x);
872+
#endif
873+
}
874+
else if constexpr (sizeof(T) == sizeof(long long int))
875+
{
876+
// NOTE: overflow is not resolved if LONG_LONG_INT_MIN value is converted
877+
#if __cplusplus >= 202302L
878+
return static_cast<std::make_unsigned_t<T>>(std::abs(static_cast<long long int>(x)));
879+
#else
880+
return static_cast<std::make_unsigned_t<T>>((x < T(0)) ? -x : x);
881+
#endif
882+
}
883+
}
884+
else
885+
{ // In C++17, the std::abs() integer overloads are only for : int, long, and long long.
886+
// unsigned type values std::abs() are supported through implicit type conversions to
887+
// the next larger sized integer type. The 'unsigned long long' failed due to an
888+
// lack of larger sized signed integer type to be promoted to.
889+
// type of x | default std::abs() called
890+
// uint8_t | std::abs( (int32_t) x)
891+
// uint16_t | std::abs( (int32_t) x)
892+
// uint32_t | std::abs( (int64_t) x)
893+
// uint64_t | // compiler error
894+
// This explicit override provides direct support for all unsigned integer types with type promotion.
895+
return x;
896+
}
897+
}
898+
else if constexpr (std::is_floating_point_v<T>) // floating point or complex<> types
899+
{ // floating point std::abs is constexpr in c++23 and later
900+
#if __cplusplus >= 202302L
901+
return std::abs(x);
902+
#else
903+
return (x < 0) ? -x : x;
904+
#endif
905+
}
866906
}
867-
inline unsigned long
868-
abs(unsigned long x)
907+
template <typename T>
908+
auto
909+
safe_abs(const std::complex<T> & x) noexcept
869910
{
870-
return x;
911+
// std::abs<T>(std::complex<T>) is not constexpr in in any proposed c++ standard
912+
return std::abs<T>(x);
871913
}
872-
// long long - target type will have width of at least 64 bits. (since C++11)
873-
inline unsigned long long
874-
abs(unsigned long long x)
914+
915+
916+
// itk::Math::abs has different behavior than std::abs. The use of
917+
// abs() in the ITK context without namespace resolution can be confusing
918+
// The abs() version is provided for backwards compatibility.
919+
template <typename T>
920+
auto
921+
abs(T x) noexcept
875922
{
876-
return x;
923+
return safe_abs(x);
877924
}
878925

879-
using std::abs;
880926

881927
} // namespace itk::Math
882928

Modules/Core/Common/test/itkMathTest.cxx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,65 @@ main(int, char *[])
853853
std::cout << "Test passed" << std::endl;
854854
}
855855

856+
// Test the itk::Math::abs methods
857+
std::cout << "Testing itk::Math::abs integral overloads" << std::endl;
858+
// Specify template arguments to avoid choosing non-constexpr std::abs overloads
859+
static_assert(itk::Math::safe_abs(false) == false);
860+
static_assert(itk::Math::safe_abs(true) == true);
861+
static_assert(itk::Math::safe_abs((unsigned char)5) == 5);
862+
static_assert(itk::Math::safe_abs((unsigned short)5) == 5);
863+
static_assert(itk::Math::safe_abs((unsigned int)5) == 5);
864+
static_assert(itk::Math::safe_abs((unsigned long)5) == 5);
865+
static_assert(itk::Math::safe_abs((unsigned long long)5) == 5);
866+
867+
static_assert(itk::Math::safe_abs((signed char)-5) == 5);
868+
static_assert(itk::Math::safe_abs((signed char)-128) == 128);
869+
870+
static_assert(itk::Math::safe_abs((short)-5) == 5);
871+
872+
static_assert(itk::Math::safe_abs<int>(-5) == 5u);
873+
static_assert(itk::Math::safe_abs<long>(-5L) == 5ul);
874+
static_assert(itk::Math::safe_abs<long long>(-5LL) == 5ull);
875+
876+
static_assert(itk::Math::safe_abs<double>(-5.0) == 5.0);
877+
static_assert(itk::Math::safe_abs<float>(-5.0f) == 5.0f);
878+
879+
// complex types are never constexpr for abs or safe_abs
880+
const auto cf = std::complex<float>(-3, -4);
881+
ITK_TEST_EXPECT_EQUAL(itk::Math::safe_abs(cf), 5);
882+
const auto cd = std::complex<double>(-3, -4);
883+
ITK_TEST_EXPECT_EQUAL(itk::Math::safe_abs(cd), 5);
884+
const auto cld = std::complex<long double>(-3, -4);
885+
ITK_TEST_EXPECT_EQUAL(itk::Math::safe_abs(cld), 5);
886+
887+
// test backward compatible non-constexpr abs function
888+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs(false), false);
889+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs(true), true);
890+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs((unsigned char)5), 5);
891+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs((signed char)-5), 5);
892+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs((signed char)-128), 128);
893+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs((short)-5), 5);
894+
// Specify template arguments to avoid choosing non-constexpr std::abs overloads
895+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs<int>(-5), 5u);
896+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs<long>(-5L), 5ul);
897+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs<long long>(-5LL), 5ull);
898+
899+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs<double>(-5.0), 5.0);
900+
ITK_TEST_EXPECT_EQUAL(itk::Math::abs<float>(-5.0f), 5.0f);
901+
902+
903+
if (itk::Math::abs((signed char)-128) != 128)
904+
{
905+
std::cout << "itk::Math::abs((signed char)-128) FAILED!" << std::endl;
906+
testPassStatus = EXIT_FAILURE;
907+
}
908+
909+
if (itk::Math::abs(-5) != 5)
910+
{
911+
std::cout << "itk::Math::abs(-5) FAILED!" << std::endl;
912+
testPassStatus = EXIT_FAILURE;
913+
}
914+
856915

857916
return testPassStatus;
858917
}

0 commit comments

Comments
 (0)