Skip to content

Commit

Permalink
Apply massive refactor and cleanup based on updated clang-tidy rules
Browse files Browse the repository at this point in the history
  • Loading branch information
Rinzii committed Mar 16, 2024
1 parent fad11fe commit 9718de7
Show file tree
Hide file tree
Showing 36 changed files with 776 additions and 306 deletions.
5 changes: 3 additions & 2 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ AccessModifierOffset: -4
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveMacros: Consecutive
AlignEscapedNewlines: Right
AlignTrailingComments: Always
AlignTrailingComments:
Kind: Always
AllowShortEnumsOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: InlineOnly
Expand Down Expand Up @@ -46,7 +47,7 @@ IncludeCategories:
# Headers in <> with .hpp extension.
- Regex: '<([A-Za-z0-9\/-_])+\.hpp>'
Priority: 20
# Headers in <> without extension.
# Headers in <> without an extension.
- Regex: '<([A-Za-z0-9\/-_])+>'
Priority: 30
NamespaceIndentation: All
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
MIT License

Copyright (c) 2024-Present Ian Pike
Copyright (c) 2024-Present cmath contributors
Copyright (c) 2024-Present CCMath contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# ccmath - A C++17 constexpr-Compatible CMath Library
# CCMath - A C++17 constexpr-Compatible CMath Library

ccmath is a C++17 library that provides a re-implementation of the standard `<cmath>` library with all features made `constexpr`. This enables compile-time evaluation of mathematical functions, improving performance and allowing for more efficient code in scenarios where constant expressions are required.
CCMath is a C++17 library that provides a re-implementation of the standard `<cmath>` library with all features made `constexpr`.
This enables compile-time evaluation of mathematical functions,
improving performance and allows for more efficient code in scenarios where constant expressions are required.

## Features

- **Full constexpr Compatibility**: All functions provided by ccmath are implemented as `constexpr` along with an active effort made to ensure all functions work within `static_assert`. The primary goal is to ensure every function can be evaluated at compile time.
- **Full constexpr Compatibility**: All functions provided by CCMath are implemented as `constexpr` along with an active effort made to ensure all functions work within `static_assert`. The primary goal is to ensure every function can be evaluated at compile time.

- **Drop In Replacement for Standard Math Library**: ccmath provides a comprehensive set of mathematical functions that are 1:1 compatible with the C++ standard library `<cmath>`. The goal of ccmath is to effectively be a drop in replacement for `<cmath>` with little to no discernable difference between the two. This includes trigonometric, exponential, logarithmic, and other common mathematical operations. If `<cmath>` has it then it is likely ccmath has implemented it.
- **Drop-in Replacement for the Standard Math Library**: CCMath provides a comprehensive set of mathematical functions that are 1:1 compatible with the C++ standard library `<cmath>`. The goal of CCMath is to effectively be a drop-in replacement for `<cmath>` with little to no discernible difference between the two. This includes trigonometric, exponential, logarithmic, and other common mathematical operations. If `<cmath>` has it then it is likely CCMath has implemented it.

- **Performance Optimization**: Besides all the functions being able to be evaluated at compile time, ccmath was also built with speed in mind. We strive to have speeds nearly as fast as the standard implementation.
- **Performance Optimization**: Besides all the functions being able to be evaluated at compile time, CCMath was also built with speed in mind. We strive to have speeds nearly as fast as the standard implementation.

- **No External Dependencies**: ccmath has no external dependencies and only requires a C++17-compliant compiler.
- **No External Dependencies**: CCMath has no external dependencies and only requires a modern C++17-compliant compiler.

## Usage

To use ccmath in your projects, simply include the ccmath.hpp header file and start using the provided functions. Here's a basic example:
To use CCMath in your projects, include the `<ccmath/ccmath.hpp>` header file and start using the provided functions.
Here's a basic example:

```cpp

Expand All @@ -28,9 +31,9 @@ int main() {
}
```

## Adding ccmath to your project
## Adding CCMath to your project

ccmath has a comprehensive cmake setup and can be easily included in your project using fetchcontent like so:
CCMath has a comprehensive cmake setup and can be easily included in your project using fetchcontent like so:

```cmake
cmake_minimum_required(VERSION 3.18)
Expand Down Expand Up @@ -168,4 +171,4 @@ CCmath is an open-source project, and it needs your help to go on growing and im

## License

ccmath is distributed under the MIT License. See the LICENSE file for more information.
CCMath is distributed under the MIT License. See the LICENSE file for more information.
23 changes: 23 additions & 0 deletions benchmark/ccmath_benchmark_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ std::vector<double> generateRandomDoubles(size_t count, unsigned int seed) {
return randomDouble;
}






static void BM_std_fma(bm::State& state) {
for (auto _ : state) {
bm::DoNotOptimize(std::fma(state.range(0), state.range(1), state.range(2)));
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_std_fma)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity();

static void BM_ccm_fma(bm::State& state) {
for (auto _ : state) {
bm::DoNotOptimize(ccm::fma(state.range(0), state.range(1), state.range(2)));
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ccm_fma)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity();



// Benchmarking std::abs with the same set of random integers
static void BM_std_abs_rand_int(benchmark::State& state) {
auto randomIntegers = generateRandomIntegers(static_cast<size_t>(state.range(0)), DefaultSeed);
Expand Down
5 changes: 5 additions & 0 deletions ccmath_headers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ set(ccmath_internal_helpers_headers
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/find_number.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/floating_point_type.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/fpclassify_helper.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/meta_compare.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/exp_helpers.hpp
)

set(ccmath_internal_predef_headers
Expand Down Expand Up @@ -83,6 +85,9 @@ set(ccmath_detail_compare_headers
)

set(ccmath_detail_exponential_impl_headers
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/detail/exponential/impl/exp_float_impl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/detail/exponential/impl/exp_double_impl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/detail/exponential/impl/exp_data.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/detail/exponential/impl/log_float_impl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/detail/exponential/impl/log_double_impl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/detail/exponential/impl/log_data.hpp
Expand Down
2 changes: 1 addition & 1 deletion include/ccmath/detail/basic/abs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace ccm
template <typename T, std::enable_if_t<std::is_unsigned_v<T>, bool> = true>
constexpr T abs(T x) noexcept
{
// If abs is called with an argument of type X for which is_unsigned_v<X> is true and
// If abs is called with an argument of type X for which is_unsigned_v<X> is true, and
// if X cannot be converted to int by integral promotion, the program is ill-formed.
// See: http://eel.is/c++draft/c.math.abs#3
// See: ISO/IEC 9899:2018, 7.12.7.2, 7.22.6.1
Expand Down
10 changes: 5 additions & 5 deletions include/ccmath/detail/basic/fdim.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ namespace ccm
* @param y A floating-point or integer values
* @return If successful, returns the positive difference between x and y.
*/
template <typename T, std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
inline constexpr T fdim(T x, T y)
{
if (ccm::isnan(x)) { return x; }
if (ccm::isnan(y)) { return y; }
if (x <= y) { return T(+0.0); }
else if ((y < T(0.0)) && (x > (std::numeric_limits<T>::max() + y))) { return std::numeric_limits<T>::infinity(); }
else { return x - y; }
if ((y < T(0.0)) && (x > (std::numeric_limits<T>::max() + y))) { return std::numeric_limits<T>::infinity(); }
return x - y;
}

/**
Expand All @@ -39,7 +39,7 @@ namespace ccm
* @param y A floating-point value.
* @return If successful, returns the positive difference between x and y.
*/
template <typename T, typename U, std::enable_if_t<std::is_floating_point<T>::value && std::is_floating_point<U>::value, int> = 0>
template <typename T, typename U, std::enable_if_t<std::is_floating_point_v<T> && std::is_floating_point<U>::value, int> = 0>
inline constexpr auto fdim(T x, U y)
{
// Find the common type of the two arguments
Expand All @@ -56,7 +56,7 @@ namespace ccm
* @param y An integral value.
* @return If successful, returns the positive difference between x and y.
*/
template <typename Integer, std::enable_if_t<std::is_integral<Integer>::value, int> = 0>
template <typename Integer, std::enable_if_t<std::is_integral_v<Integer>, int> = 0>
inline constexpr double fdim(Integer x, Integer y)
{
return fdim<double>(static_cast<double>(x), static_cast<double>(y));
Expand Down
128 changes: 50 additions & 78 deletions include/ccmath/detail/basic/fma.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,79 +15,51 @@

namespace ccm
{
/// @cond MATH_DETAIL
namespace
{
namespace impl
{
template <typename T>
inline constexpr T fma_impl_internal(T x, T y, T z) noexcept
{
// If the compiler has a builtin, use it
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__INTEL_LLVM_COMPILER)
// TODO: Add a wrapper for if constexpr
if constexpr (std::is_same_v<T, float>) { return __builtin_fmaf(x, y, z); }
else if constexpr (std::is_same_v<T, double>) { return __builtin_fma(x, y, z); }
else if constexpr (std::is_same_v<T, long double>) { return __builtin_fmal(x, y, z); }
#endif
// If the compiler doesn't have a builtin, use the following and hope that the compiler is smart enough to optimize it
return (x * y) + z;
}

// Special case for floating point types
template <typename Real, std::enable_if_t<std::is_floating_point_v<Real>, bool> = true>
inline constexpr Real fma_impl_switch(Real x, Real y, Real z) noexcept
{
// Handle infinity
if ((x == Real{0} && ccm::isinf(y)) || (y == Real{0} && ccm::isinf(x))) { return std::numeric_limits<Real>::quiet_NaN(); }
if (x * y == std::numeric_limits<Real>::infinity() && z == -std::numeric_limits<Real>::infinity())
{
return std::numeric_limits<Real>::infinity();
}

// Handle NaN
if (ccm::isnan(x) || ccm::isnan(y)) { return std::numeric_limits<Real>::quiet_NaN(); }
if (ccm::isnan(z) && (x * y != 0 * std::numeric_limits<Real>::infinity() || x * y != std::numeric_limits<Real>::infinity() * 0))
{
return std::numeric_limits<Real>::quiet_NaN();
}

return fma_impl_internal(x, y, z);
}

template <typename Integer, std::enable_if_t<std::is_integral_v<Integer>, bool> = true>
inline constexpr Integer fma_impl_switch(Integer x, Integer y, Integer z) noexcept
{
return fma_impl_internal(x, y, z);
}
} // namespace impl
} // namespace
/// @endcond

template <typename T>
/**
* @brief Fused multiply-add operation.
* @tparam T A numeric type.
* @param x A floating-point or integer value.
* @param y A floating-point or integer value.
* @param z A floating-point or integer value.
* @tparam T Numeric type.
* @param x Floating-point or integer value.
* @param y Floating-point or integer value.
* @param z Floating-point or integer value.
* @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively,
* calculated as a single ternary floating-point operation).
*/
template <typename T>
inline constexpr T fma(T x, T y, T z) noexcept
{
// the switch decides which implementation to use based on the type
return impl::fma_impl_switch(x, y, z);
if constexpr (std::is_floating_point_v<T>)
{
// Handle infinity
if ((x == T{0} && ccm::isinf(y)) || (y == T{0} && ccm::isinf(x))) { return std::numeric_limits<T>::quiet_NaN(); }
if (x * y == std::numeric_limits<T>::infinity() && z == -std::numeric_limits<T>::infinity()) { return std::numeric_limits<T>::infinity(); }

// Handle NaN
if (ccm::isnan(x) || ccm::isnan(y)) { return std::numeric_limits<T>::quiet_NaN(); }
if (ccm::isnan(z) && (x * y != 0 * std::numeric_limits<T>::infinity() || x * y != std::numeric_limits<T>::infinity() * 0))
{
return std::numeric_limits<T>::quiet_NaN();
}
}

// GCC has a constexpr builtin for fma
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__INTEL_LLVM_COMPILER) && !defined(__NVCOMPILER) && \
!defined(__NVCOMPILER_LLVM__)
if constexpr (std::is_same_v<T, float>) { return __builtin_fmaf(x, y, z); }
else if constexpr (std::is_same_v<T, double>) { return __builtin_fma(x, y, z); }
else if constexpr (std::is_same_v<T, long double>) { return __builtin_fmal(x, y, z); }
#endif
// If the compiler doesn't have a builtin, use the following and hope that the compiler is smart enough to optimize it
return (x * y) + z;
}

/**
* @brief Fused multiply-add operation.
* @tparam T A floating-point or integer type converted to a common type.
* @tparam U A floating-point or integer type converted to a common type.
* @tparam V A floating-point or integer type converted to a common type.
* @param x A floating-point or integer value converted to a common type.
* @param y A floating-point or integer value converted to a common type.
* @param z A floating-point or integer value converted to a common type.
* @tparam T Floating-point or integer type converted to a common type.
* @tparam U Floating-point or integer type converted to a common type.
* @tparam V Floating-point or integer type converted to a common type.
* @param x Floating-point or integer value converted to a common type.
* @param y Floating-point or integer value converted to a common type.
* @param z Floating-point or integer value converted to a common type.
* @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively,
* calculated as a single ternary floating-point operation).
*/
Expand All @@ -107,51 +79,51 @@ namespace ccm
std::conditional_t<UCommon <= std::numeric_limits<epsilon_type>::epsilon() && UCommon <= TCommon, U,
std::conditional_t<VCommon <= std::numeric_limits<epsilon_type>::epsilon() && VCommon <= UCommon, V, epsilon_type>>>;

return ccm::fma(static_cast<shared_type>(x), static_cast<shared_type>(y), static_cast<shared_type>(z));
return ccm::fma<shared_type>(static_cast<shared_type>(x), static_cast<shared_type>(y), static_cast<shared_type>(z));
}

/**
* @brief Fused multiply-add operation.
* @tparam T A floating-point or integer type converted to a common type.
* @tparam U A floating-point or integer type converted to a common type.
* @tparam V A floating-point or integer type converted to a common type.
* @param x A floating-point or integer value converted to a common type.
* @param y A floating-point or integer value converted to a common type.
* @param z A floating-point or integer value converted to a common type.
* @tparam T Integer type converted to a common type.
* @tparam U Integer type converted to a common type.
* @tparam V Integer type converted to a common type.
* @param x Integer value converted to a common type.
* @param y Integer value converted to a common type.
* @param z Integer value converted to a common type.
* @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively,
* calculated as a single ternary floating-point operation).
*/
template <typename T, typename U, typename V, std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U> && std::is_integral_v<V>, bool> = true>
inline constexpr auto fma(T x, U y, V z) noexcept // Special case for if all types are integers.
{
using shared_type = std::common_type_t<T, U, V>;
return ccm::fma(static_cast<shared_type>(x), static_cast<shared_type>(y), static_cast<shared_type>(z));
return ccm::fma<shared_type>(static_cast<shared_type>(x), static_cast<shared_type>(y), static_cast<shared_type>(z));
}

/**
* @brief Fused multiply-add operation.
* @param x A floating-point value.
* @param y A floating-point value.
* @param z A floating-point value.
* @param x Floating-point value.
* @param y Floating-point value.
* @param z Floating-point value.
* @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively,
* calculated as a single ternary floating-point operation).
*/
inline constexpr float fmaf(float x, float y, float z) noexcept
{
return ccm::fma(x, y, z);
return ccm::fma<float>(x, y, z);
}

/**
* @brief Fused multiply-add operation.
* @param x A floating-point value.
* @param y A floating-point value.
* @param z A floating-point value.
* @param x Floating-point value.
* @param y Floating-point value.
* @param z Floating-point value.
* @return If successful, returns the value of x * y + z as if calculated to infinite precision and rounded once to fit the result type (or, alternatively,
* calculated as a single ternary floating-point operation).
*/
inline constexpr long double fmal(long double x, long double y, long double z) noexcept
{
return ccm::fma(x, y, z);
return ccm::fma<long double>(x, y, z);
}
} // namespace ccm

Expand Down
4 changes: 2 additions & 2 deletions include/ccmath/detail/basic/fmod.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ namespace ccm
{
// The standard specifies that plus or minus 0 is returned depending on the sign of x.
if (ccm::signbit(x)) { return -static_cast<T>(0.0); }
else { return static_cast<T>(0.0); }
return static_cast<T>(0.0);
}

// If x is ±∞ and y is not NaN OR if y is ±0 and x is not NaN, -NaN is returned
if ((ccm::isinf(x) && !ccm::isnan(y)) || (y == static_cast<T>(0.0) && !ccm::isnan(x)))
{
// For some reason all the major compilers return a negative NaN even though I can't find anywhere
// For some reason, all the major compilers return a negative NaN even though I can't find anywhere
// in the standard that specifies this. I'm going to follow suit and return a negative NaN for now.
// Overall, this has little effect on checking for NaN. We only really care for conformance with the standard.
return -std::numeric_limits<T>::quiet_NaN();
Expand Down
4 changes: 2 additions & 2 deletions include/ccmath/detail/basic/max.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace ccm
template <typename T>
inline constexpr T max(T x, T y) noexcept
{
if constexpr (std::is_floating_point<T>::value)
if constexpr (std::is_floating_point_v<T>)
{
if (ccm::isnan(x) && ccm::isnan(y)) { return std::numeric_limits<T>::quiet_NaN(); }

Expand Down Expand Up @@ -92,7 +92,7 @@ namespace ccm
* @param y Right-hand side of the comparison.
* @return If successful, returns the larger of two floating point values. The value returned is exact and does not depend on any rounding modes.
*/
template <typename Integer, std::enable_if_t<std::is_integral<Integer>::value, int> = 0>
template <typename Integer, std::enable_if_t<std::is_integral_v<Integer>, int> = 0>
inline constexpr double fmax(Integer x, Integer y) noexcept
{
return max<double>(static_cast<double>(x), static_cast<double>(y));
Expand Down
2 changes: 1 addition & 1 deletion include/ccmath/detail/basic/min.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace ccm
if constexpr (std::is_floating_point_v<T>)
{
if (ccm::isnan(x)) { return y; }
else if (ccm::isnan(y)) { return x; }
if (ccm::isnan(y)) { return x; }
}

return (x < y) ? x : y;
Expand Down
Loading

0 comments on commit 9718de7

Please sign in to comment.