素朴に、いいえ。積の n 項のそれぞれを「モンゴメリー空間」に変換する必要があるため、「通常の」アルゴリズムと同じように、m を法とする n 個の完全削減があります。
ただし、階乗は単なる n 項の任意の積ではありません。それははるかに構造化されています。特に、「Montgomerized」をすでにお持ちの場合はkr mod m
、非常に安価な還元を使用して を取得でき(k+1)r mod m
ます。
したがって、これは完全に実現可能ですが、これまでに行われたことはありません。私は先に進んで、簡単な実装を書きました(非常にテストされていないため、まったく信頼できません)。
// returns m^-1 mod 2**64 via clever 2-adic arithmetic (http://arxiv.org/pdf/1209.6626.pdf)
uint64_t inverse(uint64_t m) {
assert(m % 2 == 1);
uint64_t minv = 2 - m;
uint64_t m_1 = m - 1;
for (int i=1; i<6; i+=1) { m_1 *= m_1; minv *= (1 + m_1); }
return minv;
}
uint64_t montgomery_reduce(__uint128_t x, uint64_t minv, uint64_t m) {
return x + (__uint128_t)((uint64_t)x*-minv)*m >> 64;
}
uint64_t montgomery_multiply(uint64_t x, uint64_t y, uint64_t minv, uint64_t m) {
return montgomery_reduce(full_product(x, y), minv, m);
}
uint64_t montgomery_factorial(uint64_t x, uint64_t m) {
assert(x < m && m % 2 == 1);
uint64_t minv = inverse(m); // m^-1 mod 2**64
uint64_t r_mod_m = -m % m; // 2**64 mod m
uint64_t mont_term = r_mod_m;
uint64_t mont_result = r_mod_m;
for (uint64_t k=2; k<=x; k++) {
// Compute the montgomerized product term: kr mod m = (k-1)r + r mod m.
mont_term += r_mod_m;
if (mont_term >= m) mont_term -= m;
// Update the result by multiplying in the new term.
mont_result = montgomery_multiply(mont_result, mont_term, minv, m);
}
// Final reduction
return montgomery_reduce(mont_result, minv, m);
}
通常の実装に対してベンチマークを行いました。
__uint128_t full_product(uint64_t x, uint64_t y) {
return (__uint128_t)x*y;
}
uint64_t naive_factorial(uint64_t x, uint64_t m) {
assert(x < m);
uint64_t result = x ? x : 1;
while (x --> 2) result = full_product(result,x) % m;
return result;
}
マイナーな非効率性を修正するために、いくつかのインライン asm を使用した通常の実装に対して:
uint64_t x86_asm_factorial(uint64_t x, uint64_t m) {
assert(x < m);
uint64_t result = x ? x : 1;
while (x --> 2) {
__asm__("mov %[result], %%rax; mul %[x]; div %[m]"
: [result] "+d" (result) : [x] "r" (x), [m] "r" (m) : "%rax", "flags");
}
return result;
}
私の Haswell ラップトップでは、かなり大きな x に対して次のような結果が得られました。
implementation speedup
---------------------------
naive 1.00x
x86_asm 1.76x
montgomery 5.68x
ですから、これは本当に素晴らしい勝利のようです。Montgomery 実装の codegen はかなりまともですが、手書きのアセンブリでもさらに改善される可能性があります。
これは、「適度な」x と m の興味深いアプローチです。x が大きくなると、x の線形以下の複雑さを持つさまざまなアプローチが必然的に勝ちます。factorial には非常に多くの構造があるため、この方法では利用できません。