如果x!! = 1 * 3 * 5 * 7 * 9 * 11 * ...,那么2x! = 2x!! * 2^x * x!。
这为我们提供了更有效的阶乘算法。
template<ull mod>
struct fast_fact {
ull m( ull a, ull b ) const {
ull r = (a*b)%mod;
return r;
}
template<class...Ts>
ull m( ull a, ull b, Ts...ts ) const {
return m( m( a, b ), ts... );
}
// calculates x!!, ie 1*3*5*7*...
ull double_fact( ull x ) const {
ull ret = 1;
for (ull i = 3; i < x; i+=2) {
ret = m(i,ret);
}
return ret;
}
// calculate 2^2^n for n=0...bits in ull
// a pointer to this is stored statically to make calculating
// 2^k faster:
ull const* get_pows() const {
static ull retval[ sizeof(ull)*8 ] = {2%mod};
for (int i = 1; i < sizeof(ull)*8; ++i) {
retval[i] = m(retval[i-1],retval[i-1]);
}
return retval;
}
// calculate 2^x. We decompose x into bits
// and multiply together the 2^2^i for each bit i
// that is set in x:
ull pow_2( ull x ) const {
static ull const* pows = get_pows();
ull retval = 1;
for (int i = 0; x; ++i, (x=x/2)){
if (x&1) retval = m(retval, pows[i]);
}
return retval;
}
// the actual calculation:
ull operator()( ull x ) const {
x = x%mod;
if (x==0) return 1;
ull result = 1;
// odd case:
if (x&1) result = m( (*this)(x-1), x );
else result = m( double_fact(x), pow_2(x/2), (*this)(x/2) );
return result;
}
};
template<ull mod>
ull factorial_mod( ull x ) {
return fast_fact<mod>()(x);
}
live example
更快的版本可以重复使用 x!! 的结果,因为这些结果经常重复。
Caching live example,通过合理巧妙地缓存x!! 值,对于大 n 而言,速度大约是上述速度的 2 倍。每次调用double_factorial(n) 都会创建 lg k 个缓存条目,其中 k 是 n 与最大的旧缓存条目之间的距离。因为 k 以 n 为界。在实践中,这似乎在第一次调用后将加权“缓存未命中”减少到几乎为零:n!! 的第一次计算注入了足够的缓存条目,我们不会在以后计算 !! 时花费大量时间。
这个优化版本比简单的迭代实现快了大约 41%(基本上所有时间都花在计算第一个 n!!)。
进一步的改进可能包括使第一次x!! 计算更快,优化缓存可能会带来微小的改进。下一个问题:如何让x!! 更快?