编译时(constexpr)float modulo?

时间:2013-01-12 15:12:01

标签: c++ algorithm c++11 floating-point modulo

考虑以下函数,它在编译时根据参数类型计算积分或浮点模数:

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));
}

可以改善这个功能的主体吗? (我需要为整数和浮点类型都有一个函数)。

6 个答案:

答案 0 :(得分:5)

这是清理它的一种方法:

#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>());
}

如果尝试在非算术类型上调用此函数,则会出现编译错误。

答案 1 :(得分:2)

我宁愿用这种方式定义它(模板别名+模板重载):

#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;
}

IMO比较长但更干净。我假设“单一功能”是指“可以统一调用的东西”。如果您的意思是“单个函数模板”,那么我只会保持模板别名改进并保留过载。但是,正如另一个答案中所提到的,不清楚为什么你需要只有一个功能模板。

答案 2 :(得分:1)

你问,

  

“这个功能的主体可以改进吗?”

当然可以。现在这是一个意大利面条混乱:

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));
}

你澄清了......

  

“(我需要为整数和浮点类型都有一个函数)”

模板不是单一功能。这是一个模板。函数是从它生成的。

这意味着您的问题建立在错误的假设之上。

如果删除了这个假设,那么简化函数体的一种方法是将浮点类型与其他数值类型相比较,这是你应该做的事情。为此,将函数模板实现放在一个类中(因为C ++不支持函数的部分特化,只支持类的特殊化。)

然后你可以使用各种格式化技巧,包括“0?0:blah”技巧,使函数更具可读性,包括行和缩进和东西! : - )


附录:深入研究您的代码我看到您随意投放到long intint而忽略了调用者的类型。那是不好的。编写一堆自动化测试用例,调用具有各种参数类型和大/小值的函数可能是一个好主意。

答案 3 :(得分:1)

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);
}

答案 4 :(得分:0)

我相信有更简单的方法:

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

注意:基于%的检测,因此只要它们实现运算符,也适用于自定义类型。当我在它的时候,我也把它变成了混合型。

// 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*/; }

注意:让fmod可用更干净,遗憾的是我不相信constexpr;因此,我选择为浮点类型设置非模板模,这允许您执行 magic 以根据类型的二进制表示来计算精确的模数。

答案 5 :(得分:0)

如果你愿意,你可以做得更简单:

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));
}