编译期(constexpr)浮点数取模?

6
考虑以下函数,它根据参数类型在编译时计算整数积分或浮点模数:
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));
}

这个函数的代码体能否改进?(我需要一个可以同时适用于整型和浮点型的单一函数)。


“improved”是什么意思?是缩短了吗? - Andy Prowl
只是举个例子,您可以使用模板别名来使 static_cast<typename<... 部分更易读。 - Andy Prowl
1
你不能使用模板特化来解决这个问题吗(例如,enable_if)?这将使实现更清晰,并且可以像单个函数一样调用。 - chradcliffe
专业化会更加简洁明了。 - Kerrek SB
在我看来,return (x < 0 ? -1 : 1) * (abs(x) - (int64_t)abs(x / y) * abs(y)); 更易读。 - Trass3r
显示剩余2条评论
6个回答

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

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

@MooingDuck:哦,不好意思,我错过了那个。我会记下来的。谢谢。 - Kerrek SB
1
@TBohne 没有迹象表明fmod是constexpr,然而在MinGW 4.9.2中似乎可以正常工作。这让我感到很惊讶... - Syndog
在MSVC智能感知中,这不起作用,因为fmod不是constexpr。但是它可以编译。天知道为什么。 - Tomáš Zato

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

在我看来,这种写法虽然有点冗长,但更加简洁易懂。我假设你所说的“单一函数”是指“可以统一调用的函数”。如果你是指“单一函数模板”,那么我建议仅保留模板别名改进并保留重载。但是,正如其他答案中提到的那样,为什么需要一个单一函数模板并不是很清楚。


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

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,而不考虑调用者的类型。这是不好的。最好编写一堆自动化测试用例,使用各种参数类型和大/小值来调用该函数。


2
+1:放弃有问题的原始前提是一个重要的点。 - Lightness Races in Orbit

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;因此我选择了针对浮点类型的非模板取模运算符,这使您可以执行魔法以计算基于类型的二进制表示的精确模数。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接