Vincent
Vincent

Reputation: 60341

Compile-time (constexpr) float modulo?

Consider the following function that computes the integral or floating-point modulo depending on the argument type, at compile-time:

template<typename T>
constexpr T modulo(const T x, const T y)
{
    return (std::is_floating_point<T>::value) ? (x < T() ? T(-1) : T(1))*((x < T() ? -x : x)-static_cast<long long int>((x/y < T() ? -x/y : x/y))*(y < T() ? -y : y))
    : (static_cast<typename std::conditional<std::is_floating_point<T>::value, int, T>::type>(x)
      %static_cast<typename std::conditional<std::is_floating_point<T>::value, int, T>::type>(y));
}

Can the body of this function be improved ? (I need to have a single function for both integer and floating-point types).

Upvotes: 6

Views: 3191

Answers (7)

Florian Winter
Florian Winter

Reputation: 5279

If you want to go absolutely crazy in C++23 and combine all the good parts of the answers presented here, you can build a customization point object (CPO):

#include <concepts>
#include <cmath>

namespace operators
{
    namespace detail
    {
        template<typename Strategy>
        struct Choice { Strategy strategy; bool isNoexcept{}; };

        template<auto> struct RequireConstant;

        template<typename T>
        concept FloatOrInt = std::floating_point<T> || std::integral<T>;
    }

    template<typename T, typename U>
    constexpr bool enableModulus = true;

    class Modulus
    {
        enum class Strategy { None, Adl, Int, Fmod, FmodConstexprShim };

        // Shim for MSVC where `std::fmod` is not `constexpr` as required by C++23
        template<detail::FloatOrInt T, detail::FloatOrInt U>
        static constexpr auto fmodConstexprShim(const T lhs, const U rhs) noexcept
        {
            return lhs - rhs * static_cast<int>(lhs / rhs);
        }

        template<typename T, typename U>
        static constexpr auto Choose() -> detail::Choice<Strategy>
        {
            if constexpr (not enableModulus<std::remove_cvref_t<T>, std::remove_cvref_t<U>>)
                return { Strategy::None };
            else if constexpr (requires { modulus(std::declval<T>(), std::declval<U>()); })
                return { Strategy::Adl, noexcept(modulus(std::declval<T>(), std::declval<U>()))};
            else if constexpr (requires { std::declval<T>() % std::declval<U>(); })
                return { Strategy::Int, noexcept(std::declval<T>() % std::declval<U>()) };
            else if constexpr (requires { std::fmod(std::declval<T>(), std::declval<U>()); })
            {
                if constexpr (requires { typename detail::RequireConstant<std::fmod(std::declval<T>(), std::declval<U>())>; })
                {
                    return { Strategy::Fmod, noexcept(std::fmod(std::declval<T>(), std::declval<U>())) };
                }
                else
                {
                    return { Strategy::FmodConstexprShim, noexcept(fmodConstexprShim(std::declval<T>(), std::declval<U>())) };
                }
            }
            else
                return { Strategy::None };
        }

    public:
        template<typename T, typename U>
        constexpr auto operator()(T&& lhs, U&& rhs) const noexcept(Choose<T, U>().isNoexcept)
            requires (Choose<T, U>().strategy != Strategy::None)
        {
            if constexpr (Choose<T, U>().strategy == Strategy::Adl)
                return modulus(std::forward<T>(lhs), std::forward<U>(rhs));
            else if constexpr (Choose<T, U>().strategy == Strategy::Fmod)
                return std::fmod(std::forward<T>(lhs), std::forward<U>(rhs));
            else if constexpr (Choose<T, U>().strategy == Strategy::FmodConstexprShim)
                return fmodConstexprShim(std::forward<T>(lhs), std::forward<U>(rhs));
            else
                return std::forward<T>(lhs) % std::forward<U>(rhs);
        }
    };

    constexpr Modulus modulus;
}

Runnable code with tests

Usage:

using operators::modulus;

modulus(a, b);

This does the following:

  1. a % b, if it is well-formed.
  2. std::fmod(a, b), if it is well-formed and constexpr
  3. a - b * static_cast<int>(a / b) if std::fmod(a, b) is well-formed but not constexpr (C++20 or MSVC with latest C++ implementation)

so it uses the "best" built-in methods for integers and floats and is constexpr.

Furthermore, the following can override the behaviors above:

If enableModulus<T, U> is specialized to boolean-test false for the (non-reference, non-cv-qualified) types of a and b, then modulus(a, b) does not participate in overload resolution.

This can be used as a workaround for custom types that do define a % operator, but using it would result in a compiler error, even in a concept test. An example is std::chrono::duration with floats.

// Disable `std::chrono::duration` % floats
template<typename Rep, typename Period, std::floating_point F>
constexpr bool operators::enableModulus<std::chrono::duration<Rep, Period>, F> = false;

To customize this operator, you just overload the % operator for your types as you usually would.

However, if the expression modulus(a, b) with a callable modulus found by argument-dependent lookup (ADL) is well-formed, then it is used before all the other alternatives, including an eligible overloaded % operator. This makes it possible to "fix" an existing overloaded operator% in a third-party library that doesn't work correctly for some types, such as maybe std::chrono::duration with floats :).

The customization point object (CPO) pattern ensures safe overloading and makes it possible to check in type constraints or assertions whether the modulus operator is available:

class Vector<typename T, std::size_t N>
{
    // If `x[i] % s` is defined for components `x[i]` of the vector `v`,
    // then so is `v % s`.
    template<typename S>
    auto operator%(const S& s) const
        requires requires { modulus(std::declval<T>(), s); }
    {
        // return Vector(x[0] % s, ...);
    }
}

modulus(a, b) is noexcept if and only if its underlying implementation is.

Do you need all this? Maybe. I am working on a library for concept-testing types with overloaded arithmetic operators (such as vectors, matrices, etc.), and it includes a few callable that wrap operators to supplement the ones provided by the standard library. This one is used instead of std::modulus<>.

Want a simple and more backwards-compatible solution instead? Then this answer is better than most of the complicated alternatives.

Upvotes: 0

API-Beast
API-Beast

Reputation: 793

You can do that much simpler if you want:

template<typename A, typename B>
constexpr auto Modulo(const A& a, const B& b) -> decltype(a - (b * int(a/b)))
{
    return a - (b * int(a/b));
}

Upvotes: 1

Matthieu M.
Matthieu M.

Reputation: 299730

I believe there is simpler:

// Special available `%`
template <typename T, typename U>
constexpr auto modulo(T const& x, U const& y) -> decltype(x % y) {
    return x % y;
}

Note: based on detection of % so also works for custom types as long as they implement the operator. I also made it mixed type while I was at it.

// Special floating point
inline constexpr float modulo(float x, float y) { return /*something*/; }

inline constexpr double modulo(double x, double y) { return /*something*/; }

inline constexpr long double modulo(long double x, long double y) { return /*something*/; }

Note: it would cleaner to have fmod available unfortunately I do not believe it is constexpr; therefore I chose to have non-template modulos for the floating point types which allows you to perform magic to compute the exact modulo possibly based on the binary representation of the type.

Upvotes: 0

Howard Hinnant
Howard Hinnant

Reputation: 218700

template <class T>
constexpr
T
modulo(T x, T y)
{
    typedef typename std::conditional<std::is_floating_point<T>::value,
                                        int,
                                        T
                                     >::type Int;
    return std::is_floating_point<T>() ?
              x - static_cast<long long>(x / y) * y :
              static_cast<Int>(x) % static_cast<Int>(y);
}

Upvotes: 2

Kerrek SB
Kerrek SB

Reputation: 476910

Here's one way to clean this up:

#include <type_traits>
#include <cmath>

template <typename T>  //     integral?       floating point?
bool remainder_impl(T a, T b, std::true_type, std::false_type) constexpr
{
    return a % b;  // or whatever
}

template <typename T>  //     integral?        floating point?
bool remainder_impl(T a, T b, std::false_type, std::true_type) constexpr
{
    return std::fmod(a, b); // or substitute your own expression
}

template <typename T>
bool remainder(T a, T b) constexpr
{
    return remainder_impl<T>(a, b,
             std::is_integral<T>(), std::is_floating_point<T>());
}

If you try and call this function on a type that's not arithmetic, you'll get a compiler error.

Upvotes: 7

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145194

You ask,

“Can the body of this function be improved?”

Certainly. Right now it is a spaghetti mess:

template<typename T>
constexpr T modulo(const T x, const T y)
{
    return (std::is_floating_point<T>::value) ? (x < T() ? T(-1) : T(1))*((x < T() ? -x : x)-static_cast<long long int>((x/y < T() ? -x/y : x/y))*(y < T() ? -y : y))
    : (static_cast<typename std::conditional<std::is_floating_point<T>::value, int, T>::type>(x)
      %static_cast<typename std::conditional<std::is_floating_point<T>::value, int, T>::type>(y));
}

You clarify that …

“(I need to have a single function for both integer and floating-point types)”

Well the template is not a single function. It’s a template. Functions are generated from it.

This means your question builds on a false assumption.

With that assumption removed, one way to simplify the function body, which you should do as a matter of course, is to specialize the template for floating point types versus other numeric types. To do that put the function template implementation in a class (because C++ does not support partial specialization of functions, only of classes).

Then you can employ various formatting tricks, including the "0?0 : blah" trick to make the function more readable, with lines and indentation and stuff! :-)


Addendum: delving into your code I see that you cast haphazardly to long int and int with disregard of the invoker's types. That's ungood. It is probably a good idea to write up a bunch of automated test cases, invoking the function with various argument types and big/small values.

Upvotes: 1

Andy Prowl
Andy Prowl

Reputation: 126402

I would rather define it this way (template aliases + template overloading):

#include <type_traits>

using namespace std;

// For floating point types

template<typename T, typename enable_if<is_floating_point<T>::value>::type* p = nullptr>
constexpr T modulo(const T x, const T y)
{
    return (x < T() ? T(-1) : T(1)) * (
            (x < T() ? -x : x) -
            static_cast<long long int>((x/y < T() ? -x/y : x/y)) * (y < T() ? -y : y)
            );
}

// For non-floating point types

template<typename T>
using TypeToCast = typename conditional<is_floating_point<T>::value, int, T>::type;

template<typename T, typename enable_if<!is_floating_point<T>::value>::type* p = nullptr>
constexpr T modulo(const T x, const T y)
{
    return (static_cast<TypeToCast<T>>(x) % static_cast<TypeToCast<T>>(y));
}

int main()
{
    constexpr int x = modulo(7.0, 3.0);
    static_assert((x == 1.0), "Error!");
    return 0;
}

It is lengthier but cleaner IMO. I am assuming that by "single function" you mean "something that can be invoked uniformly". If you mean "a single function template", then I would just keep the template alias improvement and leave the overload. But then, as mentioned in another answer, it would not be clear why you do need to have one single function template.

Upvotes: 2

Related Questions