问题描述
使用c ++进行RSA加密,发现cmath中的pow()函数给了我错误的结果。
在上网查看后,我遇到了一些可以为我完成上述过程的代码,但是我很难理解它。
这是代码:
long long int modpow(long long int base,long long int exp,long long int modulus) {
base %= modulus;
long long int result = 1;
while (exp > 0) {
if (exp & 1) {
result = (result * base) % modulus;
}
base = (base * base) % modulus;
exp >>= 1;
}
return result;
}
(此代码不是原始代码)
我正在努力理解此功能。
我知道exp >>=1;
是左移1位,而(exp & 1)
根据最低有效位返回1或0,但是我不明白这是如何影响最终答案的。
例如:
if (exp & 1) {
result = (result * base) % modulus;
}
如果exp为奇数,(result * base) % modulus
的目的是什么?
希望有人可以向我解释此功能,因为我不想将其复制过来。
解决方法
该代码被编写为“聪明”而不是清晰。这种神秘的风格通常不应该在核心库(性能至关重要)之外完成,即使使用它,也可以通过注释来解释正在发生的事情。 这是代码的带注释的版本。
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 /= 2
相比,在计算exp >>= 1
时有一个额外的步骤。 (我不知道为什么对exp
进行了签名;对于负指数,该函数在概念上既无意义,又在技术上不正确。)另请参见premature-optimization。
第二,我创建了一个辅助函数来提高可读性。尽管改进很小,但不影响性能。我希望值得任何值得赞扬的编译器内联该函数。
// Wrapper for detecting if an integer is odd.
bool is_odd(long long int n)
{
return n % 2 != 0;
}
第三,添加了注释以说明正在发生的情况。虽然有些人(不是我)可能认为“标准的从右到左模块化二进制幂运算算法” 是每个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 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
的类型。