平方求幂

在数学和程序设计中，平方求幂（）或快速幂是快速计算一个数（或更一般地说，一个半群的元素，如多项式或方阵）的大正整数乘幂的一般方法。这些算法可以非常通用，例如用在模算数或矩阵幂。对于通常使用加性表示法的半群，如密码学中使用的椭圆曲线，这种方法也称为double-and-add。
基本方法.
该方法是基于观察到，对于正整数formula_1，可知
该方法使用指数的位（二进制的位，即bit，下文称为“位”）来确定计算哪些幂。
此例显示如何使用此方法计算 formula_3。
幂指数13的二进制为1101。这些位按照从左到右的顺序使用。
指数有4位，所以有4次迭代。
首先，将结果初始化为1：formula_4
这可以按照下面的递归算法来实现：
 Function exp_by_squaring(x, n)
 if n &lt; 0 then return exp_by_squaring(1 / x, -n);
 else if n = 0 then return 1;
 else if n = 1 then return x ;
 else if n is even then return exp_by_squaring(x * x, n / 2);
 else if n is odd then return x * exp_by_squaring(x * x, (n - 1) / 2);
尽管不是尾调用，但是通过引入辅助函数，该算法可以被重写成尾递归算法：
 Function exp_by_squaring(x, n)
 exp_by_squaring2(1, x, n)
 Function exp_by_squaring2(y, x, n)
 if n &lt; 0 then return exp_by_squaring2(y, 1 / x, - n);
 else if n = 0 then return y;
 else if n = 1 then return x * y;
 else if n is even then return exp_by_squaring2(y, x * x, n / 2);
 else if n is odd then return exp_by_squaring2(x * y, x * x, (n - 1) / 2).
该算法的迭代版本的辅助空间是有界的，代码如下
 Function exp_by_squaring_iterative(x, n)
 if n &lt; 0 then
 x := 1 / x;
 n := -n;
 if n = 0 then return 1
 y := 1;
 while n &gt; 1 do
 if n is even then 
 x := x * x;
 n := n / 2;
 else
 y := x * y;
 x := x * x;
 n := (n – 1) / 2;
 return x * y
c++实现（非递归）
返回formula_12 对formula_13求模
long long power(long long p, long long n)
 long long ans = 1;
 while (n)
 if (n &amp; 1) ans = (ans * p) % Mod;
 p = p * p % Mod;
 n »= 1;
 return ans;
计算复杂度.
简要分析表明此算法用了 formula_14 次平方，以及至多 formula_14 次乘法，其中 formula_16 表示向下取整函数。更确切地说，做乘法的次数比 formula_1 的二进制展开的次数要少一次。对于 formula_1 大于4左右的时候，这种算法在计算上就已经比"天真地"将它与自身重复地相乘更高效了。
每次平方的结果大约是前一次结果的两倍，因此，如果两个 formula_19 位数的相乘的实现要进行 formula_20 次计算（其中 formula_21 为一固定值），那么计算 formula_22 的复杂度为：
formula_24法.
此算法先把指数展开成 formula_24 形式，然后再计算 formula_22 的值。它在1939年由Brauer首次提出。在下面的算法中，使用以下函数 formula_27 和formula_28，其中 formula_29，formula_30 为奇数。
算法：
 y := 1; i := l-1
 while i&gt;=0 do
 (s,u) := f(ni)
 for j:=1 to k-s do
 y := y2 
 y := y*xu
 for j:=1 to s do
 y := y2
 i := i-1
 return y
为了获得最佳效率，"formula_21" 应该是满足
的最小整数。
滑动窗口法.
此方法是 formula_24 法的更高效的变体。例如，要计算398次幂，二进制展开为 (110 001 110)2，采用长度为3的窗，使用 formula_24 法，需要计算 1, formula_40。
但也可以计算 1,formula_41，这就会省下一次乘法，相当于是计算 (110 001 110)n2 的值
以下是一般算法：
算法：
算法：
 y := 1; i := l-1
 while i &gt; -1 do
 if ni=0 then
 y:=y2' i:=i-1
 else
 while ns=0 do
 s:=s+1 
 for h:=1 to i-s+1 do
 y:=y2
 u:=(ni,ni-1...,ns)2
 y:=y*xu
 i:=s-1
 return y
蒙哥马利阶梯法.
求幂的许多算法都不提供对旁路攻击的防护。也就是说，监测到乘方运算的攻击者可以（部分）还原所计算的指数。就如众多公钥加密系统中那样，如果指数需要保密的话，这就是个问题了。一个叫做蒙哥马利阶梯的方法解决了这个问题。 
给定一个非零正整数的二进制展开 formula_47（其中 formula_48），可以以下面方式计算 formula_22：
 x1=x; x2=x2
 for i=k-2 to 0 do
 If ni=0 then
 x2=x1*x2; x1=x12
 else
 x1=x1*x2; x2=x22
 return x1
该算法会执行一系列固定的操作（复杂度formula_50）：无论每一位的具体值如何，指数中的每一位都会进行乘法和平方。
蒙哥马利阶梯法的这种具体实现还无法抵御缓存：当根据秘密指数的位值访问不同的变量时，内存访问延迟仍可能被攻击者观察到。
固定基底的幂.
当基底固定而指数变化时，可以使用几种方法来计算 formula_22。可以看出，预计算在这些算法中起着关键作用。
姚期智的方法.
姚期智的方法与 formula_24 法不同，是把指数以 formula_53 为基底展开，并按上面的算法进行计算。令 formula_1, formula_55, formula_56, formula_57 为整数。
把指数 formula_1 写成
令 formula_62。该算法使用等式
给定 formula_64 的元素 formula_31，指数 formula_1 写成上述形式，并且预先计算 formula_67 的值，元素 formula_22 就可以用下面的算法计算了。
 y=1,u=1 and j=h-1
 while j &gt; 0 do
 for i=0 to w-1 do
 if ni=j then u=u*xbi
 y=y*u
 j=j-1
 return y
如果令 formula_69，formula_70，那么这些 formula_55 就是 formula_1 以 formula_73 为基的每一位。姚期智的方法是把之前的那些 formula_74 收集到 formula_30 中，which appear to the highest power ；in the next round those with power are collected in formula_30 as well etc. 变量 formula_77 被原始的 formula_30 乘了 次，内第二高的指数乘了 次……
该算法计算 formula_22 要用 次乘法，存储 个元素。
欧几里得法.
欧几里德法首先在P.D Rooij的《使用预计算和向量加法链的高效求幂》（）中介绍。
这种计算群 中 formula_22（formula_1 为自然数）的方法是，递归地使用下面的等式：
给定群 中的基底元素 formula_31，把指数 formula_1 用姚期智的方法写出来，然后就可以用预计算的 formula_90 个值 formula_91 计算 formula_22 了。
 Begin loop 
 
 Break loop 
 
 End loop;
该算法首先在 formula_55 中找到最大值，在找到集合 formula_94 中的最大值。
然后递归求 formula_95 的 formula_86 次幂，把这个值乘以 formula_97，赋值给 formula_97；把 formula_99 模 formula_100 的结果赋值给 formula_99。
更多应用.
这一思路还可用于计算大指数幂除以一个数的余数。特别是在密码学中，在整数除以"q"的余数的环中计算幂是很有用处的。在群中计算整数幂也是很有用的，使用法则
该方法对所有半群都适用，通常用于计算矩阵的幂。
例如，如果使用朴素的方法，计算
将花费很长时间以及大量的存储空间：计算 13789722341，并除以2345求余。即便使用更有效的方法也需要很长时间：求13789的平方，除以2345求余，将结果乘以13789，再求余，一直往下计算。这会需要 formula_102 次模乘。
把“*”理解为 "x"*"y" = "xy" mod 2345（即相乘后取模）后，应用上面的平方求幂算法，只需要27次乘法和整数除法（所有这些都可以存储在单个机器字中）就可以完成计算了。
示例实现.
通过2的幂进行计算.
这是用Ruby写的上述算法的非递归实现。
由于低级语言会将 n=n/2 隐式向0取整，n=n-1 对那些语言来说，就是冗余的步骤了。
n[0]是n的二进制表示的最右边的位，所以如果它是1，则该数是奇数，如果它是零，则该数是偶数。它也是以2为模n的余数。
def power(x,n)
 result = 1
 while n.nonzero?
 if n[0].nonzero?
 result *= x
 n -= 1
 end
 x *= x
 n /= 2
 end
 return result
end
运行实例：计算 310.
 parameter x = 3
 parameter n = 10
 result := 1
 Iteration 1
 n = 10 -&gt; n is even
 x := x2 = 32 = 9
 n := n / 2 = 5
 Iteration 2
 n = 5 -&gt; n is odd
 -&gt; result := result * x = 1 * x = 1 * 32 = 9
 n := n - 1 = 4
 x := x2 = 92 = 34 = 81
 n := n / 2 = 2
 Iteration 3
 n = 2 -&gt; n is even
 x := x2 = 812 = 38 = 6561
 n := n / 2 = 1
 Iteration 4
 n = 1 -&gt; n is odd
 -&gt; result := result * x = 32 * 38 = 310 = 9 * 6561 = 59049
 n := n - 1 = 0
 return result
运行实例：计算 310.
 result := 3
 bin := "1010"
 Iteration for digit 2:
 result := result2 = 32 = 9
 1010bin - Digit equals "0"
 Iteration for digit 3:
 result := result2 = (32)2 = 34 = 81
 1010bin - Digit equals "1" --&gt; result := result*3 = (32)2*3 = 35 = 243
 Iteration for digit 4:
 result := result2 = ((32)2*3)2 = 310 = 59049
 1010bin - Digit equals "0"
 return result
JavaScript-Demonstration: http://home.mnet-online.de/wzwz.de/temp/ebs/en.htm 
幂的乘积的计算.
平方求幂也可用于计算2个或多个幂的乘积。
如果基础群或半群是可交换的，那么常常可以通过同时计算乘积来减少乘法的次数。
例子.
式子 a7×b5 可以分三步计算：
所以总共需要八次乘法。
更快的解法是同时计算这两个幂
总共只需要6次乘法。注意 a×b 计算了两次；结果可以在第一次计算后存储，这将乘法计数减少到5次。
有数字的例子：
如果至少有两个指数大于1的话，同时计算幂就会比单独计算减少乘法次数。
使用变换.
如果表达式在计算前进行变换，上面的例子 a7×b5 也可以只用5次乘法就计算出来：
a7×b5 = a2×(ab)5 其中 ab := a×b
这个变换可以推广成下面的方案：&lt;br&gt;
对于计算 aA×bB×...×mM×nN&lt;br&gt;
首先：定义 ab := a×b, abc = ab×c, ...&lt;br&gt;
然后：计算变换后的表达式 aA−B×abB−C×...×abc..mM−N×abc..mnN
在计算之前进行变换通常会减少乘法计数，但在某些情况下也会增加计数（请参见下面最后一个示例），因此在使用变换后的表达式进行计算之前，最好检查一下乘法的次数。
例子.
对于下面的表达式，表中显示了分开计算每个幂，在不进行变换的情况下同时进行计算，以及在变换后同时进行计算的乘法次数。
用有符号数字重新编码.
在某些计算中，如果允许负系数（也就会需要用基底的倒数）的话，只要在 formula_103 中计算倒数很快或者已经预先计算，求幂会更加高效。例如，当计算 formula_104 时，二进制方法需要 formula_105 次乘法和 formula_105 次平方。不过可以用 formula_21 次平方得到 formula_108，然后乘以 formula_109 得到 formula_104。
为此，定义以 formula_56 为基数的整数 formula_1 的为
 for to do
 
Koyama和Tsuruoka的另一种算法并不要求 formula_127 这样的条件；它仍然可以让汉明重量最小化。
替代方法及推广.
平方求幂可以看作是一个次优的算法：它通过由重复指数加倍（平方）和指数递增（乘以 formula_31）组成的加法链来计算指数。更一般地，如果允许"任何"先前计算的指数相加（通过乘以 "formula_31" 的幂），有时可以让求幂运算的乘法次数更少（但通常使用更多的内存）。formula_130 时的最少次数：
一般来说，求给定指数的最佳加法链是一个难题，因为没有已知的高效算法，所以最优链通常只用于小指数（比如，在编译器中已经预先存储了小指数幂的最佳链）。不过，有一些启发式算法，虽然不是最优的，但是由于额外的簿记工作和内存使用量的增加而导致的乘法次数少于平方求幂。无论如何，乘法的次数永远不会比 Θ(log "n") 增长得更慢，所以这些算法只能减小平方求幂的渐进复杂度的常数因子。