EDIT_ADD: 如果Will Ness是正确的,问题的目的只是在程序运行时输出连续的质数流(按暂停/中断键暂停并按任意键重新开始),没有真正希望达到那个上限,那么代码应该不带上限参数,并对第一个'i'循环使用"true"的范围检查。另一方面,如果问题想要实际打印出上限内的质数,则以下代码将更有效地使用试除法仅针对奇数进行操作,并且具有不使用内存的优势(它也可以转换为与上述相同的连续循环):
static void primesttt(ulong top_number) {
Console.WriteLine("Prime: 2");
for (var i = 3UL; i <= top_number; i += 2) {
var isPrime = true;
for (uint j = 3u, lim = (uint)Math.Sqrt((double)i); j <= lim; j += 2) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime) Console.WriteLine("Prime: {0} ", i);
}
}
首先,问题代码没有输出,因为它的循环变量是整数,而测试的限制是一个巨大的长整数,这意味着循环无法达到限制并产生内部循环
EDITED: ,其中变量'j'回环到负数; 当'j'变量回到-1时,测试的数字未通过素数测试,因为所有数字都可以被-1平均地整除
END_EDIT。即使这一点得到纠正,问题代码也会产生非常慢的输出,因为它会将大量的复合数(所有偶数加上奇数复合数)进行64位除法,并将其除以可能产生的每个素数的整个数字范围,直到那个十六次方的顶部数字。以上代码之所以有效,是因为它将计算限制在仅奇数上,并仅对当前测试数字的平方根进行模除
。这需要一个小时左右才能显示出十亿个素数,因此可以想象,展示所有素数到万亿级别(10的16次方),尤其是随着范围的增加,计算变得越来越慢,需要花费多少时间。 END_EDIT_ADD
虽然@SLaks使用Linq的单行(有点)答案是可行的,但它实际上并不是埃拉托斯特尼筛法,因为它只是
Trial Division的未优化版本,未优化的原因是它没有消除奇数素数,没有从发现的基础素数的平方开始,也没有停止对大于筛选顶部数字的平方根的基本素数进行筛选。由于存在多个嵌套枚举操作,它也非常慢。
事实上,这是对Linq聚合方法的滥用,并没有有效地使用生成的两个Linq范围中的第一个。可以通过以下方式将其变成优化的试除法,减少枚举开销:
static IEnumerable<int> primes(uint top_number) {
var cullbf = Enumerable.Range(2, (int)top_number).ToList();
for (int i = 0; i < cullbf.Count; i++) {
var bp = cullbf[i]; var sqr = bp * bp; if (sqr > top_number) break;
cullbf.RemoveAll(c => c >= sqr && c % bp == 0);
} return cullbf; }
该实现比SLaks的答案快多了,但由于列表生成、多次枚举和多次除法(暗示的模数)运算仍然很慢且占用内存。
以下是真正的埃拉托斯特尼筛法实现,运行速度约为上述方法的30倍,而且使用的内存更少,因为它每个筛选数字只使用一个位表示,并将其枚举限制在最终的迭代器序列输出中,同时优化了仅处理奇合数,以及仅从基本质数的平方开始剔除基本质数,对于最大数字的平方根,如下所示:
static IEnumerable<uint> primes(uint top_number) {
if (top_number < 2u) yield break;
yield return 2u; if (top_number < 3u) yield break;
var BFLMT = (top_number - 3u) / 2u;
var SQRTLMT = ((uint)(Math.Sqrt((double)top_number)) - 3u) / 2u;
var buf = new BitArray((int)BFLMT + 1,true);
for (var i = 0u; i <= BFLMT; ++i) if (buf[(int)i]) {
var p = 3u + i + i; if (i <= SQRTLMT) {
for (var j = (p * p - 3u) / 2u; j <= BFLMT; j += p)
buf[(int)j] = false; } yield return p; } }
以上代码在Intel i7-2700K (3.5 GHz)上计算1000万范围内的所有质数,大约需要77毫秒。
可以使用using语句调用其中任意一个静态方法,并通过静态Main方法进行测试,如下所示:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
static void Main(string[] args) {
Console.WriteLine("This program generates prime sequences.\r\n");
var n = 10000000u;
var elpsd = -DateTime.Now.Ticks;
var count = 0; var lastp = 0u;
foreach (var p in primes(n)) { if (p > n) break; ++count; lastp = (uint)p; }
elpsd += DateTime.Now.Ticks;
Console.WriteLine(
"{0} primes found <= {1}; the last one is {2} in {3} milliseconds.",
count, n, lastp,elpsd / 10000);
Console.Write("\r\nPress any key to exit:");
Console.ReadKey(true);
Console.WriteLine();
}
这将显示序列中素数的数量,最后找到的素数以及枚举到那个位置所花费的时间。
EDIT_ADD: 然而,为了产生小于一万万亿(10的16次方)的素数的枚举,需要使用分段分页方法并使用多核处理,即使使用C ++和非常高度优化的PrimeSieve,这也需要超过400小时才能仅生成找到的素数数量,并且枚举所有素数需要数十倍的时间,因此需要一年时间来完成问题要求的任务。使用未优化的试除法算法进行尝试,则需要超级长时间,即使使用像10的200万次方年这样的优化试除法算法也需要非常非常长的时间(那是两百万个零的年份!)。
难怪他的台式机在尝试时只是坐着停滞不前!!!如果他尝试了一个较小的范围,如一百万,他仍然会发现实施需要几秒钟的时间。
我在这里发布的解决方案也无法胜任,即使是最后的埃拉托斯特尼筛法,也需要大约640TB的内存才能处理该范围内的问题。
这就是为什么只有像PrimeSieve这样的分页分段方法才能处理这种指定范围的问题,即使这样也需要很长时间,可能需要几周甚至几年,除非您可以访问具有数十万核心的超级计算机。