1。它是如何工作的?
Fermat's little theorem 表示如果一个数 x 是素数,那么对于任何整数 a:
如果我们把两边都除以a,那么我们可以重写方程如下:
我打算证明如何这是有效的(你的第一个问题),因为在this wiki page 和一些 Google 搜索下有很多很好的证明(比我能提供的更好)。
2。代码与定理的关系
所以,您发布的函数会检查(2 << x - 2) % x == 1。
首先,(2 << x-2) 与写 2**(x-1) 或数学形式相同:
那是因为<< 是logical left-shift operator,更好地解释here。移位和乘以 2 的幂之间的关系特定于计算机上数字的表示方式(二进制),但归结为
我可以从两边的指数中减去 1,得到
现在,我们从上面知道 对于任何数字 a,
那么让我们说 a = 2。这给了我们
哎呀,这和2 << (x-2)一样!那么我们可以这样写:
这导致了最终的关系:
现在,mod 的数学版本看起来有点奇怪,但我们可以编写如下等效代码:
(2 << x - 2) % x == 1
这就是关系。
3。方法的准确性
所以,我认为“准确度”在这里是一个不好的词,因为费马小定理对于所有素数都是正确的。但是,这 not 是否意味着它对所有数字都是真或假——也就是说,如果我有一些数字 i,我不确定是否 i 是素数,使用费马的小关系只会告诉我它是否绝对不是素数。如果 Fermat 的小关系为真,则 i 不可能是素数。这些类型的数字被称为pseudoprime numbers,或者在本例中更具体地称为Fermat Pseudoprime 数字。
如果这类事情听起来很有趣,请看一下Carmichael numbers AKA 绝对费马伪素数,它在任何基础上都通过了费马测试,但不是素数。在我们的例子中,我们遇到了以 2 为底的数字,但费马小定理可能不适用于其他底的这些数字——卡迈克尔数通过了与 x 互质的所有碱基的测试。
在 Carmichael 的 wiki 页面上,讨论了它们在自然数范围内的分布——尽管指数小于 1(大约1/3)。因此,如果您要在大范围内搜索素数,您将遇到成倍增加的 Carmichael 数,这实际上是这种方法 CheckIfProbablyPrime 的误报。这可能没问题,具体取决于您的输入以及您对误报的关心程度。
4。为什么这很有用?
简而言之,这是一种优化。
使用这样的东西的主要原因是加快对素数的搜索。那是因为实际上检查一个数字是否是素数是昂贵的——即超过 O(1) 的运行时间。可行,但仍然比 O(1) 时间更昂贵。因此,如果我们可以避免对某些数字进行实际检查,我们将能够投入更多时间来检查实际候选人。由于 Fermat 的小关系只会在一个数字可能是素数时才说是(如果数字是素数,它永远不会说不),并且它可以在 O(1) 时间内检查,我们可以将它扔到一个 is_prime 循环中忽略相当数量的数字。所以,我们可以加快速度。
像这样的素数检查有很多,你可以找到一些编码的素数检查器here
最后说明
关于此优化的一个令人困惑的事情是它使用位移运算符<< 而不是求幂运算符**。这是因为位移是计算机可以执行的最快操作之一,而求幂则要慢一些。在很多情况下是not always the best optimization,因为大多数现代语言都知道如何用更优化的操作来替换我们编写的东西。但是,对于为什么这段代码的作者使用位移而不是2**(x-1),这是我的冒险。
编辑:正如 MarkDickinson 所指出的,取一个数字的指数然后显式修改它并不是最好的方法。这是一个叫做modular exponentiation 的东西,并且存在可以比我们编写它的方式更快的算法。 Python 的内置 pow 实际上实现了其中一种算法,并为 mod by 提供了一个可选的第三个参数。所以我们可以写出这个函数的最终版本:
def CheckIfProbablyPrime(x):
return pow(2, x-1, x) == 1
这不仅比令人困惑的移位废话更具可读性而且更快。 You know what they say.