C-确定数字是否为质数


75

我试图提出一个方法,该方法接受一个整数并返回一个布尔值,以表明数字是否为质数,并且我对C的了解不多。有人愿意给我一些指示吗?

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

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

1
当然,这更多是数学问题而不是编程问题?
bdonlan

49
这里有一些指针:int * ptr; int * ptr2; 诠释* ptr3。对不起没办法。您要检查的数字有多少?而且,您是否需要启发式或始终有效的方法?
AlbertoPL

3
拿出你的算法(你测试它没有代码的方式),然后也许我们可以帮助表达它C.
BobbyShaftoe

12
当您将“ i <数字”作为执行循环的条件时,“ i!=数字”有什么意义?
Matthieu M.

3
另请注意,检查i < number是过大的。根据定义,如果一个数字x = a * b,要么a或者b< int(sqrt(x))另一种是更大的。因此,您的循环只需要上升到即可int(sqrt(x))
twalberg

Answers:


153

好,所以就不用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。boolC中没有类型,notrue或no false,因此您需要对其进行一些修改(编辑: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 <= 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;
}

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


11
仅供参考,C99标准定义了一个文件<stdbool.h>头提供booltruefalse
克里斯托弗·约翰逊

27
我知道,计算平方比平方根更简单,但是在每次迭代中计算平方应该比一次计算平方根还要花很多钱:x
Matthieu M.

6
在现代乱序机器上,mul指令平方的延迟应完全隐藏在模数的延迟中,因此不会有明显的性能优势。在严格按顺序排列的机器上,使用提升的平方根是有好处的,但是如果代码在具有大int类型(64位或更大)的平台上编译,则可能会引起浮点不精确的问题。 。所有这些都可以处理,但是我认为最好使事情简单且易于携带。毕竟,如果您关心速度,那么根本就不会使用此算法。
斯蒂芬·佳能

5
@Tom您可以通过停在地板上(sqrt(number))来提高很多。以11为例,例如floor(sqrt(11))=3。3之后的数字是4、3 * 4 = 12>11。如果您使用的是朴素的筛子来检查素数,则只需检查奇数除2以外的数字,均达到原始的
sqrt。– Calyth

3
-1。最终函数给出了4294967291的错误答案。
davidg 2014年

27

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

使用Eratosthenes筛

细节:

  1. 基本上非质数可以被除1以外的其他数字整除
  2. 因此:非质数将是质数的乘积。

Eratosthenes的筛子找到质数并将其存储。当检查新数字的质数时,将根据已知质数列表检查所有先前的质数。

原因:

  1. 该算法/问题被称为“尴尬地并行
  2. 它创建素数的集合
  3. 它是一个动态编程问题的例子
  4. 快!

8
它也在O(n)空间中,并且只要您的计算是针对单个值的,这都是巨大的空间浪费,并且不会提高性能。
R .. GitHub停止帮助ICE,

3
(实际上O(n log n)或更大,如果您支持大量…)
R .. GitHub停止帮助ICE,

2
谁只为应用程序的生命周期计算素数的1个值?素数是一个很好的缓存对象。
2011年

2
一个在一个查询后终止的命令行程序就是一个明显的例子。无论如何,保持全球状态是丑陋的,应该始终被认为是一种权衡。我什至可以说(在运行时生成的)筛子实际上是无用的。如果主要候选对象足够小,以至于您可以在内存中放入一个如此大小的筛子,则应该只具有一个static const位号为主要数字的位图并使用它,而不是在运行时填充它。
R .. GitHub STOP HELPING ICE,

1
Eratosthenes的筛子是解决“生成n个质数的所有质数”问题的一种好方法。这是解决“ n个素数?”问题的浪费方式。
hobbs 2014年

16

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

  • 通过观察所有素数都为6k±1的形式(2和3除外),可以进一步改进算法。
  • 这是因为对于某个整数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的所有数字。
  • 这是测试所有直到√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; 
        }
    }
    

2
您应该0在(number == 1)时返回,因为1不是质数。
艾哈迈德·易卜拉欣

此类优化与IMO无关:该任务为何以6k±1的形式停止(2和3除外),当您可以让n ^ 4 mod 30 = 1除外(2,3)时,它以n ^ 2 mod 6 = 1进行重述,5 ...实际上,您可以永远走下去,因为您使用质数来进行此优化...这就是更通用的Eratosthenes算法
筛网的

1
@GhilesZ:我不同意,这与问题非常相关,并且使用单个“ ||” 使基本循环的运行速度快3倍。
verdy_p

此外,对于number == 1,它正确返回了0(非素数),带有经过测试的配置“(number%2 == 0)”,这是完全没有错误的
verdy_p

Eratosthene methoid是一种完全不同的方法,它需要分配一个大的O(n)布尔数组,并且由于索引访问而不一定更快。该代码很好,因为它首先优化了两个第一个素数2和3的情况(这就是循环步进2 * 3的原因)。
verdy_p

10
  1. 建立一个小的素数表,并检查它们是否除以您的输入数字。
  2. 如果数字存活到1,请尝试增加基础的伪素数测试。参见Miller-Rabin素数测试
  3. 如果您的数字存活到2,则可以得出结论,如果该数字低于某些众所周知的界限,则它是质数。否则,您的答案只会是“大概是素数”。您将在Wiki页面中找到这些范围的一些值。

3
+1:完全可以消除发问者的询问,但仍然可以纠正。
斯蒂芬·佳能

请注意,Guy L.最近也建议在答案中使用Miller-Rabin ,并链接到rosettacode.org/wiki/Miller-Rabin_primality_test#C,该示例显示了使用GMP的C语言实现。该条目还具有许多其他多种语言的实现。
Jonathan Leffler

4

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

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;
}

1
要测试素数,您应该从i=2到一直进行i<=ceil(sqrt(n))。您在您的测试错过了2号:首先,投给(int)品牌sqrt(n)主干小数。其次,您使用i<sq了应该使用的时间i<=sq。现在,假设有一个适合这个问题的数字。n具有ceil(sqrt(n))较小因子的复合数字。您的内部循环会像(5、7),(11、13),(17、19),(23、25),(29、31),(35、37),(41、43),依此类推,n%i等等n%(i+2)。假设我们得到了sqrt(1763)=41.98。作为1763=41*43一个复合数字。您的循环只会运行直到(35, 37)失败。
DrBeco

@DrBeco不错的观察!谢谢,例如。更新了代码。
GorvGoyl

2
在仔细分析了ceil()问题之后,我意识到尽管很多网站都推荐它,但是这只是过分的了。您可以中继并测试i<=sqrt(n),就可以了。测试用例是较大的补间质数。示例:86028221*86028223=7400854980481283sqrt(7400854980481283)~86028222。而较小的已知补间质数23,使sqrt(6)=2.449树干仍会离开2。(但是较小的不是测试用例,只是为了说明一点的比较)。因此,是的,该算法现在是正确的。无需使用ceil()
DrBeco

3

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

如果模量等于零,则不是质数。

伪代码:

bool IsPrime(int target)
{
  for (i = 2; i <= root(target); i++)
  {
    if ((target mod i) == 0)
    {
      return false;
    }
  }

  return true;
}

2
当然,缺点是sqrt是在每次迭代时计算的,这会使它减慢很多速度。
Rich Bradshaw

9
任何合理的编译器都应该能够检测root(target)是循环不变式并将其提升。
斯蒂芬·佳能

1
(并且,如果您的编译器无法执行此优化,则应绝对提交一个错误,以使编译器作者知道他们缺少此优化。)
Stephen Canon 2009年

以及许多其他潜在的(微)优化程序,如果您在for语句之前手动获取sqrt,则也可以检查该sqrt的mod(如果为0,则返回false)。
马特·莱西

2
如果目标值为1怎么办?
ffffff01 2012年

3

在阅读了这个问题之后,我对以下事实感到好奇:一些答案通过运行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,因为收益并不那么大。而且,如果愿意编码,则此列表还可以。问题是你不能说出所有的情况下没有环,或者你的代码将是非常大的(会有1658879 ORs,即||在相应的内部if)。下一个列表:

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原始在这里都失败了,因为101 % (11+90)
vp_arth '16

1
需要停下来n%(i+86)检查一下n > i+k
vp_arth's

干得好,先生。我会看一看。谢谢。check235()素数
7、11、13、17、19、23

解决方案:需要在以下情况下将这些提醒移到数组中,进行迭代并中断迭代i+arr[k] >= n
vp_arth

我考虑过了,但是我不想要一个数组,因为if编译器可以更好地优化with常量。我进行了编辑,以添加异常并保持当前结构完整。但我同意,用阵列对人眼会更好。
DrBeco

2

我要补充一点,没有偶数(第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;
}

1
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)。另一方面,乘法非常便宜。


1

避免溢出错误

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作为最大整数的平方根的地板上。例如65535,当unsigned是32位时。

使用for (i=2; i*i<=number; i++),出现10年的故障是因为当UINT_MAX_SQRT*UINT_MAX_SQRT <= numbernumber是素数时,下一次迭代会导致乘法溢出。如果类型是带符号的类型,则溢出为UB。对于unsigned类型,它本身不是UB,但逻辑已损坏。继续进行交互,直到截断的产品超过为止number。结果可能不正确。带32位unsigned,请尝试4,294,967,291,这是一个素数。

如果some_integer_type_MAX曾经是Mersenne Prime,那i*i<=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;
}

0

与“已知范围的”素数算法相比,使用Eratosthenes的Sieve可以更快地进行计算。

通过使用其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)需要21s 758ms。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.