你需要两个构建块来进行此计算:
- 在编译时计算整数的n次方
- 在编译时计算整数的n次根
注意:我使用int作为分子和分母的类型以节省一些输入,希望主要观点得到传达。我从一个工作的实现中提取了以下代码,但我不能保证我不会在某些地方犯错;)
第一个构建块相当容易:您可以使用x^(2n) = x^n * x^n或者x^(2n+1) = x^n * x^n * x。这样,您可以实例化最少的模板,例如x^39可以像这样计算:
x39 = x19 * x19 * x
x19 = x9 * x9 * x
x9 = x4 * x4 * x
x4 = x2 * x2
x2 = x1 * x1
x1 = x0 * x
x0 = 1
template <int Base, int Exponent>
struct static_pow
{
static const int temp = static_pow<Base, Exponent / 2>::value;
static const int value = temp * temp * (Exponent % 2 == 1 ? Base : 1);
};
template <int Base>
struct static_pow<Base, 0>
{
static const int value = 1;
};
第二种方法有点棘手,需要使用括号算法:
给定x和N,我们想要找到一个数字r,使得r^N = x。
1. 设置包含解的区间[low, high]为[1, 1 + x / N]
2. 计算中点 mean = (low + high) / 2
3. 确定 mean^N 是否大于等于 x
- 如果是,则将区间设置为 [low, mean]
- 如果不是,则将区间设置为 [mean+1, high]
4. 如果区间只包含一个数字,则计算完成
5. 否则,重复上述步骤
该算法可以得出最大的整数s,满足 s^N <= x
因此,检查 s^N 是否等于 x。如果是,则x的N次方根是整数,否则不是。
现在让我们将其编写为编译时程序:
基本接口:
template <int x, int N>
struct static_root : static_root_helper<x, N, 1, 1 + x / N> { };
helper:
template <int x, int N, int low, int high>
struct static_root_helper
{
static const int mean = (low + high) / 2;
static const bool is_left = calculate_left<mean, N, x>::value;
static const int value = static_root_helper<x, N, (is_left ? low : mean + 1), (is_left ? mean, high)>::value;
};
递归的终点,当区间仅包含一个条目时:
template <int x, int N, int mid>
struct static_root_helper<x, N, mid, mid>
{
static const int value = mid;
};
检测乘法溢出的辅助程序(您可以使用c++11 constexpr-numeric_limits替换boost-header,我认为)。如果乘法 a * b 会导致溢出,则返回true。
#include "boost/integer_traits.hpp"
template <typename T, T a, T b>
struct mul_overflow
{
static_assert(std::is_integral<T>::value, "T must be integral");
static const bool value = (a > boost::integer_traits<T>::const_max / b);
};
现在我们需要实现calculate_left函数,该函数计算x^N的解是否在平均值的左边或右边。我们希望能够计算任意根,因此像static_pow > x这样的朴素实现会很快溢出并给出错误的结果。因此我们使用以下方案:
我们想要计算x^N是否大于B。
- 设置A = x和i = 1
- 如果A >= B,则已经完成-> A ^ N肯定大于B
- A * x会溢出吗?
- 如果是-> A ^ N肯定大于B
- 如果不是-> A * = x和i + = 1
- 如果i == N,我们完成了,并可以将其与B进行简单比较
现在让我们将其写成元编程形式。
template <int A, int N, int B>
struct calculate_left : calculate_left_helper<A, 1, A, N, B, (A >= B)> { };
template <int x, int i, int A, int N, int B, bool short_circuit>
struct calulate_left_helper
{
static const bool overflow = mul_overflow<int, x, A>::value;
static const int next = calculate_next<x, A, overflow>::value;
static const bool value = calculate_left_helper<next, i + 1, A, N, B, (overflow || next >= B)>::value;
};
当i等于N时的终端点
template <int x, int A, int N, int B, bool short_circuit>
struct calculate_left_helper<x, N, A, N, B, short_circuit>
{
static const bool value = (x >= B);
};
短路终端点
template <int x, int i, int A, int N, int B>
struct calculate_down_helper<x, i, A, N, B, true>
{
static const bool value = true;
};
template <int x, int A, int N, int B>
struct calculate_down_helper<x, N, A, N, B, true>
{
static const bool value = true;
};
帮助计算 x * A 的下一个值,考虑到 x 溢出以消除编译器警告:
template <int a, int b, bool overflow>
struct calculate_next
{
static const int value = a * b;
};
template <int a, int b>
struct calculate_next<a, b, true>
{
static const int value = 0;
};
所以,就是这样。我们需要另外一位助手。
template <int x, int N>
struct has_integral_root
{
static const int root = static_root<x, N>::value;
static const bool value = (static_pow<root, N>::value == x);
};
现在我们可以按照以下方式实现ratio_pow:
template <typename, typename> struct ratio_pow;
template <int N1, int D1, int N2, int D2>
struct ratio_pow<std::ratio<N1, D1>, std::ratio<N2, D2>>
{
static_assert(has_integral_root<std::ratio<N1, D1>::num, std::ratio<N2, D2>::den>::value, "numerator has no integral root");
static_assert(has_integral_root<std::ratio<N1, D1>::den, std::ratio<N2, D2>::den>::value, "denominator has no integral root");
static const int num1 = static_root<std::ratio<N1, D1>::num, std::ratio<N2, D2>::den>::value;
static const int den1 = static_root<std::ratio<N1, D1>::den, std::ratio<N2, D2>::den>::value;
static const bool positive_exponent = std::ratio<N2, D2>::num >= 0;
static const int num2 = positive_exponent ? num1 : den1;
static const int den2 = positive_exponent ? den1 : num1;
static const int exp = positive_exponent ? std::ratio<N2, D2>::num : - std::ratio<N2, D2>::num;
typedef std::ratio<static_pow<num2, exp>::value, static_pow<den2, exp>::value> type;
};
所以,我希望至少基本思想能够传达出来。
ratio_multiply
可以实现这个功能。但是,如果整数溢出会发生什么“失败”我不知道你的意思。你想如何让它失败? - David1/2
,还是可以更加复杂,比如1/1337
? - anatolygR1
和R2
可以是任何有效的std::ratio
(例如1/1337
)。如果结果是无理数(例如2^(1/2)
),则元函数在实例化期间应崩溃。 - Vincent(X % prime == 0)
进行部分特化。如果为真,则您有一个因子,并继续使用X/prime
;如果不是,则继续使用X % nextPrime
。质数列表可以硬编码,这很不可能改变 ;) - MSalters