用C语言思想改写的用筛法求质数程序(第2修订版)

原创 2004年03月02日 10:25:00

注:这是本人去年非典期间在vccode上发表的拙作,近日看到这里人气不错,重贴给同好探讨,勿怪

http://www.vccode.com/file_show.php?id=1852

http://www.vccode.com/file_download.php?id=1852 源代码

原创: 本文及程序可免费使用,请勿用于商业用途

原算法思想:
1)先假定所有数都是质数,直到证明它不是。
2)通过筛除所有比较小的质数的倍数来找到质数。

改进一:
利用“大于2的质数都是奇数”这一知识,将存放待筛整数空间减少一半,只存储奇数。

改进二:
由于不再存有偶数,筛出的必然是奇数和奇数之积,乘数从3开始每次增2,将循环次数再减少一半

改进三:
(可能只能在C语言里这么做),利用位(bit)存放各数是否质数的标志,将存放空间减少到1/8
虽然每次循环的位运算步骤增加了,但是申请内存的时间大大减少,而且能求出更大的质数
定义了2个宏
#define getisp(i) (isprime[(i)/8]>>((i)%8))&0x1
用来取得第i个数是否质数标记
#define setisp(i,j) j?
(isprime[(i)/8]|=(0x1<<((i)%8))):
(isprime[(i)/8]&=~(0x1<<((i)%8)))
用来设置第i个数是否质数标记
不用函数表示的原因是为了较少的堆栈操作

改进四:
在设置第i个数的标记前先判断它是否已经设置为合数,如果已经设置为合数,则不重新设置
由于读操作快于写操作,这个改进对速度提高关系很大

改进五:
(1)把位运算宏中的/8操作改为>>3,把%8操作改为&0x7,
(2)由于只有一处是把数3的标记设为1,而且也不必要,把setisp宏分解,省略了设置值是1或0的判断,
(3)为了减少除法操作次数,把循环变量的值作了代数变换,但程序的可读性下降很多
这些改进对速度提高关系不大

改进六:
(1)每次筛除某个质数的倍数时,因为乘数小于该质数的积都已经被作为小于该质数的积被筛除了,
因此倍数应该从该质数开始,如:
筛除5的倍数,15已经作为3的倍数被筛除了,应该从25开始。这是算法的改进。
(2)考虑到位操作取得第i个数是否质数标记由4步运算组成,而用一个中间变量判断3的倍数只需要
3步运算。(一次自增,一次判断,判断成立再赋值一次),把判断3的倍数单独提出来。
至于大于3的质数的倍数,由于单独判断的语句多于用宏判断,得不偿失,故不再额外处理。
上述算法中其实3的倍数的存储空间也是可以节省的,但处理语句复杂化,速度上得不偿失,故也不再额外处理。

替换的核心语句如下:
由于程序的可读性原因附上了功能相同的语句作为注释:

      char _3times=0;
      i=(max-1)/2;
      for(int j = 4; j <=i ; j +=3) //set all 3’s times
      {
         isprime[(j)>>3]&=~(0x1<<((j)&0x7));
      }
      char i1=0;
       int m= (n-1)/2;
      int k=(max-1)/2;
      for( i = 2; i <= m; i++)
      {
         if(++i1==3) //skip when i is 3’s times
         {
           i1=0;
           continue;
         }
        if(getisp(i))
        {
           _3times=((i<<2)+1)%3;  //(2*(2*i+1)%3-1)

           for(int j = i*(2*i+2); j <= k; j = j +i +i+1)
            {
              if(++_3times==3) //skip when j is 3’s times
             {
                // printf("skip i=%d,j=%d%,%d
",2*i+1,2*j+1,_3times);
                _3times=0;
                continue;
             }
              if(getisp(j)) //good idea
               isprime[(j)>>3]&=~(0x1<<((j)&0x7));
             //else
              //ct++;
            }
          }
       }
      /*
       for( i = 5; i <= n; i+=2)
       {
         if(++i1==3) //skip when i is 3’s times
         {
           i1=0;
           continue;
         }
         int m=(i-1)/2;

         if (getisp(m))      // If i is a prime,
         {
             _3times=(i+i)%3-1; //用于定位j第几次是3的倍数(nod * nod )%3 all ==1
             for(int j = i*i; j <= max; j = j +i +i) // loop through multiples,
             {
               if(++_3times==3) //skip when j is 3’s times
               {
                 // printf("skip i=%d,j=%d%,%d
",i,j,_3times);
                 _3times=0;
                 continue;
               }
               int k=(j-1)/2;// they are not prime.
               if(getisp(k)) //good idea
               {
                     isprime[(k)>>3]&=~(0x1<<((k)&0x7)); // they are not prime.
               }
               else
               {
                 //printf("cat i=%d,j=%d%,%d
",i,j,_3times);
                 ct++;
               }
             }
           }
         }
       */
改进七:
昨天读了Java语法,发现它也支持位运算,但不支持宏定义,有更清晰的类型检查,如整数不能默认转化为boolean
于是,在改进六C程序的基础上,把isprime的类型 unsigned char* 改为 byte[],同时作为类数据成员变量。

static byte[] isprime;
把getisp改成如下的函数:
public static int getisp(int i)
    {
    return (isprime[(i)>>3]>>((i)&0x7))&0x1;
    }

调用时用if(getisp(j)==1)代替if(getisp(j)).
虽然Java的byte类型是有符号的,但没有影响程序的正确运行。
结果令人振奋,运行速度达到了改进3的C程序的水平。考虑到函数调用的开销,如果都改为展开的语句,
由于Java不支持if((isprime[(k)>>3]>>((k)&0x7))&0x1)==1)这样的表达式,必须先把位运算的结果赋值给某int变量f,
再判断表达式f==1是否成立,结果速度变化不明显。
补充:
利用ms java sdk 4.0提供的jexegen把.class文件转化为exe文件(但要求.class必须使用Ms的jvc而不能用Sun Jdk的javac编译,而且只能用到
jdk 1.1的功能)可提高Java程序速度,不过这已经不是纯粹的Java程序了
C:sieve>jexegen  /out:Sieve4.exe /main:Sieve4 Sieve4.class

测试速度方法:
命令行方式,用批处理test 程序名 最大整数的格式可看到程序执行前后的时间
例如:test java Sieve 50000000
注:这个时间包括class调入内存,解释的时间,对Java不太公平
测试结果:
平台:PIII650 128M windows 2003 jdk 1.4.1_02 lccwin32 ver3.3
C程序测试命令行
C:>test SieveX 12345678
Java程序测试命令行
C:>test Java SieveX 12345678
运算结果都是:
The largest prime less than or equal to 12345678 is 12345653,
平均用时如下:
/*------------原始java--------7.08 s------*/

/*------------改进一、二java------3.02 s--------*/

/*------------改进一、二c------2.34 s--------*/

/*------------改进三--------1.51 s------*/

/*------------改进四、五------1.12 s--------*/

/*------------改进六java------2.30 s--------*/
/*------------改进六java exe------1.64 s--------*/

/*------------改进七java------1.53 s--------*/
/*------------改进七java exe------1.14 s--------*/
结论:
1.算法的改进远比语句或指令的改进对提高程序的运行效率重要,除法指令没有我们想象中慢;
2.Java和C程序的运行速度有时候很接近;

C语言我能做到的改进就这么些了,下一步该用汇编了,可是我不会写汇编程序:(
数学上有没有更好的求质数的定理可用呢?我不知道,欢迎大家指正。
因为本程序主要用来说明求质数的问题,有些细节没有考虑,如最大数是0、1时的处理。
还有些地方,如x*3是否改成(x<<1)+x,j=j+i+i+1是否改成j=j+1+(i<<1),我觉得
不了解编译器的优化情况,就不自作多情了。
说明:
由于受int类型的取值范围和malloc空间限制,程序1-2能计算的质数不应超过10^8
程序3-5能计算的质数不应超过2*10^8,否则可能造成系统崩溃。

附件:
原Java程序 Sieve.java
你在编译时可以去掉package com.davidflanagan.examples.basics;一行,否则你需要设置
CLASSPATH环境变量,并把java源程序放在特定的目录才能用java com.davidflanagan.examples.basics.Sieve 2
去掉上述行的Java程序 Sieve1.java
包括改进一、二的Java程序 Sieve2.java
从改进Java程序改写的C程序 Sieve2.c
包括改进三的C程序 Sieve3.c
包括改进四、五的C程序 Sieve4.c
包括改进四、五、六的C程序 Sieve5.c
包括改进一、二、六(1)的java程序 Sieve4.java
包括改进七的java程序 Sieve5.java
批处理 test.bat
注:为了在VC下通过编译,需要用cpp后缀来表示C程序,在lcc下需要用c后缀

筛法求素数优化

之前在一篇博客http://blog.csdn.net/once_hnu/article/details/6302283看到了一个筛法求素数优化的程序,于是好奇写了一下思路,留着以后看什么题用用。都在...
  • yslcl12345
  • yslcl12345
  • 2016年03月28日 21:34
  • 375

求素数: 一般线性筛法 + 快速线性筛法

From: http://blog.csdn.net/dinosoft/article/details/5829550 素数总是一个比较常涉及到的内容,掌握求素数的方法是一项基本功。 基本原则...
  • JoeBlackzqq
  • JoeBlackzqq
  • 2015年03月11日 14:53
  • 1057

筛法高效求素数

【问题描述】:    试编写一个程序,找出2->N之间的所有质数。希望用尽可能快的方法实现。 【问题分析】:    这个问题可以有两种解法:一种是用“筛子法”,另一种是“除余法”。    如果...
  • qq_24451605
  • qq_24451605
  • 2015年02月01日 14:22
  • 692

初学筛法(c语言)

筛法
  • u013240038
  • u013240038
  • 2014年09月22日 21:45
  • 1003

算法之素数筛法

方法一 //判断是否是一个素数 int IsPrime(int a){ //0,1,负数都是非素数 if(a ...
  • SJF0115
  • SJF0115
  • 2013年03月20日 09:08
  • 7879

素数判定(素数筛法)(欧拉)

这里主要说一下素数筛法,该方法可以快速的选取出1~N数字中的所有素数。时间复杂度远小于O(N*sqrt(N)) 方法为:从2开始,往后所有素数的倍数都不是素数。最后剩下的数都是素数。 再说说欧拉公...
  • hynuacmlshk
  • hynuacmlshk
  • 2016年07月20日 15:35
  • 697

使用c语言完成了一个求素数的程序

有题目要求完成一款"求素数"的程序,由于之前有思考过这个问题,此程序求"千万内"素数不挂...
  • dalerkd
  • dalerkd
  • 2015年03月15日 09:51
  • 2570

欧拉线性筛法求素数(顺便实现欧拉函数的求值)

我们先来看一下最经典的埃拉特斯特尼筛法。时间复杂度为O(n loglog n) int ans[MAXN]; void Prime(int n) { int cnt=0; memset(prime...
  • NK_test
  • NK_test
  • 2015年05月29日 23:02
  • 7422

#C语言#关于求素数的思路(一般筛法到线性筛)

Target:输入一个正整数n,输出1~n的所有素数 让我们再来回顾一下求素数的算法,关于素数的算法是信息学竞赛和程序设计竞赛中常考的数论知识,希望通过此次对算法思路的整理能对大家有所帮助。...
  • liamleec
  • liamleec
  • 2017年11月13日 20:02
  • 112

C语言求质数的算法

qianya 上次被出了一题质数的C语言求解题目,当时用了最粗暴的算法,回来仔细参考资料,其实答案有很多种: 1,小学生版本:判断 x 是否为质数,就从 2 一直算到 x-1。 static rt_u...
  • kyo34080800
  • kyo34080800
  • 2014年06月13日 12:38
  • 7353
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:用C语言思想改写的用筛法求质数程序(第2修订版)
举报原因:
原因补充:

(最多只允许输入30个字)