代码被编写为“聪明”而不是清晰。这种神秘的风格通常不应该在核心库之外进行(性能至关重要),即使使用它,让 cmets 解释正在发生的事情也会很好。
这是代码的注释版本。
long long int modpow(long long int base, long long int exp, long long int modulus)
{
base %= modulus; // Eliminate factors; keeps intermediate results smaller
long long int result = 1; // Start with the multiplicative unit (a.k.a. one)
// Throughout, we are calculating:
// `(result*base^exp)%modulus`
while (exp > 0) { // While the exponent has not been exhausted.
if (exp & 1) { // If the exponent is odd
result = (result * base) % modulus; // Consume one application of the base, logically
// (but not actually) reducing the exponent by one.
// That is: result * base^exp == (result*base)*base^(exp-1)
}
base = (base * base) % modulus; // The exponent is logically even. Apply B^(2n) == (B^2)^n.
exp >>= 1; // The base is squared and the exponent is divided by 2
}
return result;
}
现在更有意义了吗?
对于那些想知道如何使这个神秘代码更清晰的人,我提供以下版本。主要有三项改进。
首先,按位运算已被等效的算术运算取代。如果要证明该算法有效,则将使用算术,而不是按位运算符。事实上,无论数字如何表示,该算法都有效——不需要“位”的概念,更不用说“位运算符”了。因此,实现该算法的自然方法是使用算术。使用按位运算符会消除清晰度,几乎没有任何好处。编译器足够聪明,可以生成相同的机器代码,但有一个例外。由于exp 被声明为long long int 而不是long long unsigned,因此与exp >>= 1 相比,计算exp /= 2 时有一个额外的步骤。 (我不知道为什么 exp 被签名;对于负指数,该函数在概念上和技术上都是不正确的。)另见 premature-optimization。
其次,我创建了一个帮助函数以提高可读性。虽然改进很小,但它没有性能成本。我希望任何有价值的编译器都能内联该函数。
// Wrapper for detecting if an integer is odd.
bool is_odd(long long int n)
{
return n % 2 != 0;
}
第三,添加了 cmets 来解释发生了什么。虽然有些人(不是我)可能认为“标准的从右到左模块化二进制求幂算法”是每个 C++ 编码人员都需要的知识,但我更愿意对可能阅读的人做更少的假设我将来的代码。尤其是如果那个人是我,在远离它多年后回到代码。
现在是代码,因为我希望看到编写的当前功能:
// Returns `(base**exp) % modulus`, where `**` denotes exponentiation.
// Assumes `exp` is non-negative.
// Assumes `modulus` is non-zero.
// If `exp` is zero, assumes `modulus` is neither 1 nor -1.
long long int modpow(long long int base, long long int exp, long long int modulus)
{
// NOTE: This algorithm is known as the "right-to-left binary method" of
// "modular exponentiation".
// Throughout, we'll keep numbers smallish by using `(A*B) % C == ((A%C)*B) % C`.
// The first application of this principle is to the base.
base %= modulus;
// Intermediate results will be stored modulo `modulus`.
long long int result = 1;
// Loop invariant:
// The value to return is `(result * base**exp) % modulus`.
// Loop goal:
// Reduce `exp` to the point where `base**exp` is 1.
while (exp > 0) {
if ( is_odd(exp) ) {
// Shift one factor of `base` to `result`:
// `result * base^exp == (result*base) * base^(exp-1)`
result = (result * base) % modulus;
//--exp; // logically happens, but optimized out.
// We are now in the "`exp` is even" case.
}
// Reduce the exponent by increasing the base: `B**(2n) == (B**2)**n`.
base = (base * base) % modulus;
exp /= 2;
}
return result;
}
生成的机器代码几乎相同。如果性能真的很关键,我可以回到 exp >>= 1,但前提是不允许更改 exp 的类型。