【问题标题】:Explain a code to check primality based on Fermat's little theorem解释基于费马小定理检查素数的代码
【发布时间】:2015-06-18 04:53:58
【问题描述】:

我发现一些 Python 代码声称基于 Fermat's little theorem 检查素数:

def CheckIfProbablyPrime(x):
    return (2 << x - 2) % x == 1

我的问题:

  1. 它是如何工作的?
  2. 它与费马小定理有什么关系?
  3. 这种方法的准确性如何?
  4. 如果不准确,使用它有什么好处?

我找到了here

【问题讨论】:

  • 这是一个非常直接的定理实现,使用a == 2。它使用位移运算符来执行2**x,阅读运算符here
  • 大部分内容我都听不懂,但这有帮助吗?:math.stackexchange.com/questions/659529/…
  • @Marius 好的,所以他这样做是为了提高性能。仍然为什么使用 2 ?它不适用于任何素数吗?没区别?
  • @jakekimds 谢谢,这是对定理工作原理的简短解释。
  • @bigOTHER 我相信 341、561、645 和 2 不适用。

标签: python math primes prime-factoring


【解决方案1】:

1。它是如何工作的?

Fermat's little theorem 表示如果一个数 x 是素数,那么对于任何整数 a

如果我们把两边都除以a,那么我们可以重写方程如下:

我打算证明如何这是有效的(你的第一个问题),因为在this wiki page 和一些 Google 搜索下有很多很好的证明(比我能提供的更好)。

2。代码与定理的关系

所以,您发布的函数会检查(2 &lt;&lt; x - 2) % x == 1

首先,(2 &lt;&lt; x-2) 与写 2**(x-1) 或数学形式相同:

那是因为&lt;&lt;logical left-shift operator,更好地解释here。移位和乘以 2 的幂之间的关系特定于计算机上数字的表示方式(二进制),但归结为

我可以从两边的指数中减去 1,得到


现在,我们从上面知道 对于任何数字 a

那么让我们说 a = 2。这给了我们

哎呀,这和2 &lt;&lt; (x-2)一样!那么我们可以这样写:

这导致了最终的关系:


现在,mod 的数学版本看起来有点奇怪,但我们可以编写如下等效代码:

(2 &lt;&lt; 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


最后说明

关于此优化的一个令人困惑的事情是它使用位移运算符&lt;&lt; 而不是求幂运算符**。这是因为位移是计算机可以执行的最快操作之一,而求幂则要慢一些。在很多情况下是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.

【讨论】:

  • 不错的答案。还值得注意的是,对于大型 xpow(2, x-1, x) 将比(2 &lt;&lt; x - 2) % x 更有效地(时间和空间)。
  • @bigOTHER:如果测试返回False,那么输入肯定不是奇数。如果它返回True,则测试是不确定的:它可能是质数,也可能不是质数。毫不奇怪,2 在这里是一个特例,但您询问的其他数字 (341, 561, 645) 都是复合数字。
  • 移位不是我的专长,但如果x 很大,计算(2 &lt;&lt; x-2)%x 的计算是否更好?这是您试图找到模数的大量位,使用传统的试除法会更好吗?
  • @Dan:很好的答案,但您对卡迈克尔数的定义不正确。以 2 为底的伪素数是该测试声称是素数的复合物。卡迈克尔数是对任何基本声明的测试为素数的复合数。
  • 让我告诉你一个更有趣的话题。 pow (2, x-1, x) == 1 当给出一些偶数时,这个表达式也返回“True”。您可以在此处访问这些号码的列表。 oeis.org/a006935。因此,如下更新代码会更正确。 return pow (2, x-1, x) == 1 && x% 2 == 0。这还不够,因为您还应该检查伪素数,如 341、561、645。所以代码的最终版本应该如下所示这。返回 pow (2, x-1, x) == 1 && x% 2 == 0 && binary_search_in (x, A001567) == False。可以在下面找到小于 2 ^ 64 的伪素数列表。
【解决方案2】:

我相信,您示例中的代码不正确,因为二进制左移运算符不等于费马小定理中使用的数字的幂。以 2 为底,二进制左移将等于 x + 1 的幂,这在 Fermat 的小格式版本中没有使用。

相反,在 Python 中使用 ** 表示整数的幂。

def CheckIfProbablyPrime(x):
    return (2 ** x - 2) % x == 0

“p - a 是 p 的整数倍”因此对于素数,根据定理,x 的 2 次方除以 x 的结果将留下 0(模 '%' 检查后剩余的数字分工。

对于 x - 1 版本,

def CheckIfProbablyPrime(a, x):
   return (a ** (x-1) - 1) % x == 0

对于素数,这两种变体都应为真,因为它们在 Python 中代表费马小定理

【讨论】:

  • 我认为你误解了2 &lt;&lt; x - 2。减法按运算顺序优先,所以这是2 &lt;&lt;(x-2) 而不是(2&lt;&lt;x)-22&lt;&lt;(x-2)1&lt;&lt;(x-1)2**(x-1) 相同。
猜你喜欢
  • 2013-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
  • 2018-08-13
相关资源
最近更新 更多