C - 判断一个数是不是为素数

Posted

技术标签:

【中文标题】C - 判断一个数是不是为素数【英文标题】:C - determine if a number is primeC - 判断一个数是否为素数 【发布时间】:2010-12-05 01:11:32 【问题描述】:

我正在尝试提出一种方法,该方法接受一个整数并返回一个布尔值来表示该数字是否为素数,而且我不太了解 C;有人愿意给我一些指点吗?

基本上,我会在 C# 中这样做:

static bool IsPrime(int number)

    for (int i = 2; i < number; i++)
    
        if (number % i == 0 && i != number)
            return false;
    
    return true;

【问题讨论】:

这肯定是一道数学题而不是编程题? 这里有一些提示: int *ptr;诠释*ptr2;整数 *ptr3。抱歉没办法。您要检查的数字有多大?此外,您想要启发式还是始终有效的方法? 想出你的算法(你不用代码测试它的方式)然后也许我们可以帮助用 C 来表达它。 当你有 'i 还要注意检查i &lt; number 是多余的。根据定义,如果一个数字x = a * b,则ab&lt; int(sqrt(x)),另一个更大。所以你的循环应该只需要上升到int(sqrt(x)) 【参考方案1】:

好的,所以忘记 C。假设我给你一个数字并要求你确定它是否是素数。你怎么做呢?清楚地写下这些步骤,然后担心将它们翻译成代码。

一旦你确定了算法,你就会更容易弄清楚如何编写程序,并让其他人帮助你。

编辑:这是您发布的 C# 代码:

static bool IsPrime(int number) 
    for (int i = 2; i < number; i++) 
        if (number % i == 0 && i != number) return false;
    
    return true;

这是非常接近有效的 C 原样; C 中没有 bool 类型,也没有 truefalse,因此您需要稍微修改它(编辑:Kristopher Johnson 正确指出 C99 添加了 stdbool.h 标头)。由于有些人无法访问 C99 环境(但您应该使用一个!),让我们做一个非常小的更改:

int IsPrime(int number) 
    int i;
    for (i=2; i<number; i++) 
        if (number % i == 0 && i != number) return 0;
    
    return 1;

这是一个完全有效的 C 程序,可以满足您的需求。我们可以在不费力气的情况下稍微改进一下。首先注意i总是小于number,所以检查i != number总是成功;我们可以摆脱它。

此外,您实际上不需要一直尝试除数直到number - 1;当您到达 sqrt(number) 时,您可以停止检查。由于sqrt 是一个浮点运算并且会带来很多微妙之处,因此我们实际上不会计算sqrt(number)。相反,我们可以检查 i*i &lt;= number:

int IsPrime(int number) 
    int i;
    for (i=2; i*i<=number; i++) 
        if (number % i == 0) return 0;
    
    return 1;

不过,还有最后一件事;您的原始算法中有一个小错误!如果number 为负数、零或一,此函数将声称该数是素数。您可能希望正确处理该问题,并且您可能希望将 number 设为无符号,因为您更有可能只关心正值:

int IsPrime(unsigned int number) 
    if (number <= 1) return 0; // zero and one are not prime
    unsigned int i;
    for (i=2; i*i<=number; i++) 
        if (number % i == 0) return 0;
    
    return 1;

这绝对不是检查数字是否为素数的最快方法,但它有效,而且非常简单。我们几乎不需要修改您的代码!

【讨论】:

仅供参考,C99 标准定义了一个 标头,该标头提供 booltruefalse 我知道计算一个平方比计算一个平方根更简单,但是在每次迭代中计算一个平方应该比计算一次平方根花费更多:x 在现代乱序机器上,mul 指令对 i 平方的延迟应该完全隐藏在模数的延迟中,因此不会有明显的性能提升。在严格有序的机器上,使用提升的平方根是有好处的,但是如果代码是在具有大 int 类型(64 位或更大)的平台上编译的,则可能会引发浮点不精确的问题.所有这些都可以处理,但我认为最好保持简单且易于携带。毕竟,如果你关心速度,你根本就不会使用这个算法。 @Tom 你可以通过停在地板上(sqrt(number))来提高很多。以 11 为例,floor(sqrt(11)) = 3。3 后面的数是 4,3*4 = 12 > 11。如果你用朴素筛子来检查素数,你只需要检查奇数除了 2 之外,数字最多为原始的 sqrt。 -1。最后一个函数给出了4294967291的错误答案。【参考方案2】:
    建立一个小素数表,并检查它们是否能整除您的输入数。 如果数字存活到 1,请尝试增加基数的伪素性测试。例如,请参阅Miller-Rabin primality test。 如果您的数字幸存到 2,那么如果它低于某个众所周知的界限,您可以断定它是素数。否则你的答案只会是“可能是素数”。您可以在 wiki 页面中找到这些边界的一些值。

【讨论】:

+1:对于提问者的问题完全矫枉过正,但仍然正确。 请注意,Guy L. 最近也建议在 answer 中使用 Miller-Rabin,并链接到 rosettacode.org/wiki/Miller-Rabin_primality_test#C — 它显示了在 C 中使用 GMP 的实现。该条目还具有多种其他语言的许多实现。【参考方案3】:

检查每个整数的模数,从 2 到要检查的数字的根。

如果模数等于 0,那么它不是素数。

伪代码:

bool IsPrime(int target)

  for (i = 2; i <= root(target); i++)
  
    if ((target mod i) == 0)
    
      return false;
    
  

  return true;

【讨论】:

当然,缺点是每次迭代都会计算sqrt,会拖慢很多。 任何合理的编译器都应该能够检测到 root(target) 是一个循环不变量并提升它。 (如果您的编译器无法进行该优化,您绝对应该提交一个错误,让编译器编写者知道他们缺少此优化。) 以及许多其他潜在的(微)优化,如果您在 for 语句之前手动获取 sqrt,您也可以检查它的 mod(如果为 0,则返回 false)。 如果目标值为1怎么办?【参考方案4】:

我很惊讶没有人提到这一点。

使用Sieve Of Eratosthenes

详情:

    基本上非素数可以被除 1 和它们自身之外的另一个数整除 因此:非素数将是素数的乘积。

Eratosthenes 的筛子找到一个素数并将其存储。当检查一个新数字的素数时,所有先前的素数都会根据已知素数列表进行检查。

原因:

    此算法/问题称为“Embarrassingly Parallel” 它创建一个质数集合 这是一个动态规划问题的例子 很快!

【讨论】:

在空间上也是O(n),只要你的计算是针对单个值的,这就是巨大的空间浪费,没有性能提升。 (如果您支持大量数字,则实际上是 O(n log n) 或更大...) 谁在应用程序的生命周期内只计算一个素数的值?素数是一个很好的缓存候选对象。 在一个查询后终止的命令行程序就是一个明显的例子。在任何情况下,保持全局状态都是丑陋的,应该始终被视为一种权衡。我什至会说筛子(在运行时生成)基本上是无用的。如果您的主要候选者足够小,以至于您可以在内存中放置一个该大小的筛子,那么您应该只拥有一个 static const 位图,其中数字是素数并使用它,而不是在运行时填充它。 埃拉托色尼筛法是解决“生成直到 n 的所有素数”问题的好方法。这是解决“是n素数吗?”问题的一种浪费方式【参考方案5】:

我只想补充一点,没有偶数(小节 2)可以是素数。这会导致 for 循环之前的另一个条件。所以最终代码应该是这样的:

int IsPrime(unsigned int number) 
    if (number <= 1) return 0; // zero and one are not prime
    if ((number > 2) && ((number % 2) == 0)) return 0; //no even number is prime number (bar 2)
    unsigned int i;
    for (i=2; i*i<=number; i++) 
        if (number % i == 0) return 0;
    
    return 1;

【讨论】:

【参考方案6】:
int is_prime(int val)

   int div,square;

   if (val==2) return TRUE;    /* 2 is prime */
   if ((val&1)==0) return FALSE;    /* any other even number is not */

   div=3;
   square=9;    /* 3*3 */
   while (square<val)
   
     if (val % div == 0) return FALSE;    /* evenly divisible */
     div+=2;
     square=div*div;
   
   if (square==val) return FALSE;
   return TRUE;

2 和偶数的处理被排除在只处理奇数除以奇数的主循环之外。这是因为奇数模偶数总是会给出非零答案,这使得这些测试变得多余。或者,换句话说,一个奇数可能能被另一个奇数整除,但绝不能被一个偶数整除(E*E=>E, E*O =>E, O*E=>E 和 O*O=>O)。

除法/模数在 x86 架构上确实很昂贵,尽管成本各不相同(请参阅http://gmplib.org/~tege/x86-timing.pdf)。另一方面,乘法相当便宜。

【讨论】:

【参考方案7】:

斯蒂芬·佳能回答得很好!

但是

可以通过观察除 2 和 3 之外的所有素数的形式为 6k ± 1 来进一步改进算法。 这是因为对于某个整数 k 和 i = -1、0、1、2、3 或 4,所有整数都可以表示为 (6k + i); 2 除 (6k + 0), (6k + 2), (6k + 4);和 3 个除法 (6k + 3)。 因此更有效的方法是测试 n 是否可被 2 或 3 整除,然后检查 6k ± 1 ≤ √n 形式的所有数字。

这是测试所有 m 到 √n 的速度的 3 倍。

int IsPrime(unsigned int number) 
    if (number <= 3 && number > 1) 
        return 1;            // as 2 and 3 are prime
    else if (number%2==0 || number%3==0) 
        return 0;     // check if number is divisible by 2 or 3
    else 
        unsigned int i;
        for (i=5; i*i<=number; i+=6) 
            if (number % i == 0 || number%(i + 2) == 0) 
                return 0;
        
        return 1; 
    

【讨论】:

当 (number == 1) 时,您应该返回 0,因为 1 不是质数。 这类优化与 IMO 无关:为什么要以 6k ± 1 except 2 和 3 形式停止,它在 n^2 mod 6 中重新编写= 1,当你可以有 n^4 mod 30 = 1 except 2,3,5 ...事实上,你可以永远走下去,因为你使用素数来做这种优化......这就是更一般的埃拉托色尼筛算法的原理:) @GhilesZ:我不同意,这与问题非常相关,并且只有一个“||”使基本循环有效地运行速度提高 3 倍。 除了 number==1 之外,它正确返回 0(非素数)和经过测试的条件“(number%2==0)”,si 根本没有错误 Eratosthene 方法是一种完全不同的方法,它需要分配一个大的 O(n) 布尔数组,并且由于索引访问,它不一定会更快。这段代码很好,因为它首先优化了两个第一个素数 2 和 3 的情况(这就是循环步进 2*3 的原因)。【参考方案8】:

这个程序对于检查单个数字进行素数检查非常有效。

bool check(int n)
    if (n <= 3) 
        return n > 1;
    

    if (n % 2 == 0 || n % 3 == 0) 
        return false;
    
        int sq=sqrt(n); //include math.h or use i*i<n in for loop
    for (int i = 5; i<=sq; i += 6) 
        if (n % i == 0 || n % (i + 2) == 0) 
            return false;
        
    

    return true;

【讨论】:

要测试一个素数,你应该从i=2一直走到i&lt;=ceil(sqrt(n))。您在测试中错过了 2 个数字:首先,转换为 (int) 使 sqrt(n) 中继成为小数。其次,你使用了i&lt;sq,而它应该是i&lt;=sq。现在,假设一个数字适合这个问题。以 ceil(sqrt(n)) 作为较小因子的复合数 n。你的内循环运行我喜欢:(5, 7), (11, 13), (17, 19), (23, 25), (29, 31), (35, 37), (41, 43),依此类推,n%in%(i+2)。假设我们得到sqrt(1763)=41.98。成为1763=41*43 一个合数。您的循环只会运行到(35, 37) 并失败。 @DrBeco 很好的观察!例如,谢谢。更新了代码。 在仔细分析ceil()问题后,我意识到虽然很多网站都推荐它,但它只是矫枉过正。您可以只使用i&lt;=sqrt(n) 进行中继和测试,就可以了。测试用例是大补间素数。示例:86028221*86028223=7400854980481283sqrt(7400854980481283)~86028222。而较小的已知补间素数23 给出了sqrt(6)=2.449,trunked 仍将离开2。 (但更小的不是测试用例,只是一个比较来说明问题)。所以,是的,算法现在是正确的。无需使用ceil()【参考方案9】:

阅读此问题后,我对某些答案通过运行 2*3=6 的倍数的循环提供优化这一事实很感兴趣。

所以我用同样的想法创建了一个新函数,但是是 2*3*5=30 的倍数。

int check235(unsigned long n)

    unsigned long sq, i;

    if(n<=3||n==5)
        return n>1;

    if(n%2==0 || n%3==0 || n%5==0)
        return 0;

    if(n<=30)
        return checkprime(n); /* use another simplified function */

    sq=ceil(sqrt(n));
    for(i=7; i<=sq; i+=30)
        if (n%i==0 || n%(i+4)==0 || n%(i+6)==0 || n%(i+10)==0 || n%(i+12)==0 
           || n%(i+16)==0 || n%(i+22)==0 || n%(i+24)==0)
            return 0;

        return 1;

通过运行这两个函数并检查时间,我可以说这个函数确实更快。让我们看看有 2 个不同素数的 2 个测试:

$ time ./testprimebool.x 18446744069414584321 0
f(2,3)
Yes, its prime.    
real    0m14.090s
user    0m14.096s
sys     0m0.000s

$ time ./testprimebool.x 18446744069414584321 1
f(2,3,5)
Yes, its prime.    
real    0m9.961s
user    0m9.964s
sys     0m0.000s

$ time ./testprimebool.x 18446744065119617029 0
f(2,3)
Yes, its prime.    
real    0m13.990s
user    0m13.996s
sys     0m0.004s

$ time ./testprimebool.x 18446744065119617029 1
f(2,3,5)
Yes, its prime.    
real    0m10.077s
user    0m10.068s
sys     0m0.004s

所以我想,如果一概而论,有人会获得太多吗?我想出了一个函数,它首先会进行围攻以清除给定的原始素数列表,然后使用此列表计算更大的列表。

int checkn(unsigned long n, unsigned long *p, unsigned long t)

    unsigned long sq, i, j, qt=1, rt=0;
    unsigned long *q, *r;

    if(n<2)
        return 0;

    for(i=0; i<t; i++)
    
        if(n%p[i]==0)
            return 0;
        qt*=p[i];
    
    qt--;

    if(n<=qt)
        return checkprime(n); /* use another simplified function */

    if((q=calloc(qt, sizeof(unsigned long)))==NULL)
    
        perror("q=calloc()");
        exit(1);
    
    for(i=0; i<t; i++)
        for(j=p[i]-2; j<qt; j+=p[i])
            q[j]=1;

    for(j=0; j<qt; j++)
        if(q[j])
            rt++;

    rt=qt-rt;
    if((r=malloc(sizeof(unsigned long)*rt))==NULL)
    
        perror("r=malloc()");
        exit(1);
    
    i=0;
    for(j=0; j<qt; j++)
        if(!q[j])
            r[i++]=j+1;

    free(q);

    sq=ceil(sqrt(n));
    for(i=1; i<=sq; i+=qt+1)
    
        if(i!=1 && n%i==0)
            return 0;
        for(j=0; j<rt; j++)
            if(n%(i+r[j])==0)
                return 0;
    
    return 1;

我假设我没有优化代码,但这是公平的。现在,测试。因为动态内存太多,我预计列表 2 3 5 会比硬编码的 2 3 5 慢一点。但是没关系,你可以在下面看到。在那之后,时间越来越短,最终最好的名单是:

2 3 5 7 11 13 17 19

8.6 秒。因此,如果有人要创建一个使用这种技术的硬编码程序,我建议使用列表 2 3 和 5,因为收益并不大。而且,如果愿意编码,这个列表是可以的。问题是你不能在没有循环的情况下陈述所有情况,或者你的代码会非常大(在各自的内部if 中会有 1658879 ORs,即 ||)。下一个列表:

2 3 5 7 11 13 17 19 23

时间开始变长,只有 13 秒。这里是整个测试:

$ time ./testprimebool.x 18446744065119617029 2 3 5
f(2,3,5)
Yes, its prime.
real    0m12.668s
user    0m12.680s
sys     0m0.000s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7
f(2,3,5,7)
Yes, its prime.
real    0m10.889s
user    0m10.900s
sys     0m0.000s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7 11
f(2,3,5,7,11)
Yes, its prime.
real    0m10.021s
user    0m10.028s
sys     0m0.000s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7 11 13
f(2,3,5,7,11,13)
Yes, its prime.
real    0m9.351s
user    0m9.356s
sys     0m0.004s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7 11 13 17
f(2,3,5,7,11,13,17)
Yes, its prime.
real    0m8.802s
user    0m8.800s
sys     0m0.008s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7 11 13 17 19
f(2,3,5,7,11,13,17,19)
Yes, its prime.
real    0m8.614s
user    0m8.564s
sys     0m0.052s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7 11 13 17 19 23
f(2,3,5,7,11,13,17,19,23)
Yes, its prime.
real    0m13.013s
user    0m12.520s
sys     0m0.504s

$ time ./testprimebool.x 18446744065119617029 2 3 5 7 11 13 17 19 23 29
f(2,3,5,7,11,13,17,19,23,29)                                                                                                                         
q=calloc(): Cannot allocate memory

PS。我没有故意释放(r),将此任务交给操作系统,因为一旦程序退出,内存就会被释放,以获得一些时间。但如果您打算在计算后继续运行代码,最好释放它。


奖金

int check2357(unsigned long n)

    unsigned long sq, i;

    if(n<=3||n==5||n==7)
        return n>1;

    if(n%2==0 || n%3==0 || n%5==0 || n%7==0)
        return 0;

    if(n<=210)
        return checkprime(n); /* use another simplified function */

    sq=ceil(sqrt(n));
    for(i=11; i<=sq; i+=210)
        
        if(n%i==0 || n%(i+2)==0 || n%(i+6)==0 || n%(i+8)==0 || n%(i+12)==0 || 
   n%(i+18)==0 || n%(i+20)==0 || n%(i+26)==0 || n%(i+30)==0 || n%(i+32)==0 || 
   n%(i+36)==0 || n%(i+42)==0 || n%(i+48)==0 || n%(i+50)==0 || n%(i+56)==0 || 
   n%(i+60)==0 || n%(i+62)==0 || n%(i+68)==0 || n%(i+72)==0 || n%(i+78)==0 || 
   n%(i+86)==0 || n%(i+90)==0 || n%(i+92)==0 || n%(i+96)==0 || n%(i+98)==0 || 
   n%(i+102)==0 || n%(i+110)==0 || n%(i+116)==0 || n%(i+120)==0 || n%(i+126)==0 || 
   n%(i+128)==0 || n%(i+132)==0 || n%(i+138)==0 || n%(i+140)==0 || n%(i+146)==0 || 
   n%(i+152)==0 || n%(i+156)==0 || n%(i+158)==0 || n%(i+162)==0 || n%(i+168)==0 || 
   n%(i+170)==0 || n%(i+176)==0 || n%(i+180)==0 || n%(i+182)==0 || n%(i+186)==0 || 
   n%(i+188)==0 || n%(i+198)==0)
            return 0;
    
    return 1;

时间:

$ time ./testprimebool.x 18446744065119617029 7
h(2,3,5,7)
Yes, its prime.
real    0m9.123s
user    0m9.132s
sys     0m0.000s

【讨论】:

奖励:101-199 primals 在这里都失败了,因为101 % (11+90) 需要停在n%(i+86)或查看n &gt; i+k 干得好,先生。我会看看。谢谢你。对于素数 7、11、13、17、19、23 和 29,函数 check235() 也会出现同样的问题 解决方案:需要将这些提醒移动到一个数组中,在i+arr[k] &gt;= n时遍历并中断迭代 我想过,但我不想要一个数组,因为带有常量的if 可以被编译器更好地优化。我进行了编辑以添加异常并保持当前结构不变。但我同意,用数组对人眼会更好。【参考方案10】:

使用埃拉托色尼筛法,与“已知范围”的素数算法相比,计算速度要快得多。

通过使用它的 wiki (https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) 中的伪代码,我可以在 C# 上找到解决方案。

public bool IsPrimeNumber(int val) 
    // Using Sieve of Eratosthenes.
    if (val < 2)
    
        return false;
    

    // Reserve place for val + 1 and set with true.
    var mark = new bool[val + 1];
    for(var i = 2; i <= val; i++)
    
        mark[i] = true;
    

    // Iterate from 2 ... sqrt(val).
    for (var i = 2; i <= Math.Sqrt(val); i++)
    
        if (mark[i])
        
            // Cross out every i-th number in the places after i (all the multiples of i).
            for (var j = (i * i); j <= val; j += i)
            
                mark[j] = false;
            
        
    

    return mark[val];

IsPrimeNumber(1000000000) 需要 21 秒 758 毫秒。

注意:数值可能因硬件规格而异。

【讨论】:

【参考方案11】:

避免溢出错误

unsigned i, number;
...
for (i=2; i*i<=number; i++)   // Buggy
for (i=2; i*i<=number; i += 2)   // Buggy
// or
for (i=5; i*i<=number; i+=6)  // Buggy

number 是质数且i*i 接近类型的最大值时,这些形式不正确。

所有整数类型都存在问题,signed, unsigned 和更广泛的类型。

例子:

UINT_MAX_SQRT 作为最大整数值的平方根的下限。例如。当unsigned 为 32 位时为 65535。

对于for (i=2; i*i&lt;=number; i++),这个 10 年前的故障会发生,因为当 UINT_MAX_SQRT*UINT_MAX_SQRT &lt;= numbernumber 是质数时,下一次迭代会导致乘法溢出。如果类型是 signed 类型,则溢出是 UB。对于无符号类型,这本身不是UB,但逻辑已经崩溃。交互一直持续到截断乘积超过number。可能会出现不正确的结果。使用 32 位 unsigned,尝试 4,294,967,291,这是一个素数。

如果some_integer_type_MAX 是Mersenne Prime,i*i&lt;=number永远不会为真。


为避免此错误,请考虑number%inumber/i 在许多编译器上都很高效,因为商和余数的计算是一起完成的,因此两者都不会产生额外的成本,而只是 1。

一个简单的全方位解决方案:

bool IsPrime(unsigned number) 
    for(unsigned i = 2; i <= number/i; i++)
        if(number % i == 0)
            return false;
        
    
    return number >= 2;

【讨论】:

以上是关于C - 判断一个数是不是为素数的主要内容,如果未能解决你的问题,请参考以下文章

C语言 输出50-100间的所有素数,其中判断一个数是不是为素数用函数完成。

50~100之间的素数,判断是不是为素数用函数完成

C++判断素数的代码

新手求教 c语言素数判断算法

C语言中判断N个数中的素数并求和

C语言中素数的判断方法