EDIT_ADD: 如果 Will Ness 是正确的,那么问题的目的只是在程序运行期间输出一个连续的素数流(按 Pause/Break 暂停和任何键重新开始),没有认真的希望每次都能到达那个上限,那么代码应该写成没有上限参数和第一个“i”for循环的“真”范围检查。另一方面,如果问题想要实际打印质数到一个极限,那么下面的代码将更有效地完成这项工作,只对奇数使用试除法,其优点是它根本不使用内存(也可以根据上述转换为连续循环):
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);
}
}
首先,问题代码不产生输出,因为它的循环变量是整数,并且测试的限制是一个巨大的长整数,这意味着循环不可能达到产生内部循环的限制编辑: 变量'j'循环回到负数;当“j”变量回到 -1 时,被测试的数字未通过质数测试,因为所有数字都可以被 -1 整除 END_EDIT. 即使纠正了这一点,问题代码也会产生非常慢的输出,因为它被绑定了对非常大量的合数(所有偶数加上奇数的复合数)进行 64 位除法,直到该顶部的整个数字范围对于它可能产生的每个素数,数字 10 的 16 次方。上面的代码之所以有效,是因为它将计算限制为仅奇数,并且只对被测试的当前数字的平方根进行模除法。
将素数显示到十亿需要一个小时左右,因此可以想象将所有素数显示到一万万亿(10 的 16 次方)所需的时间,尤其是在计算速度变慢的情况下随着范围的增加。 END_EDIT_ADD
尽管@SLaks 使用Linq 的一个线性(某种)答案有效,但它并不是真正的Eratosthenes 筛子,因为它只是Trial Division的未优化版本,未优化因为它不会消除奇数素数,不会开始在找到的基本素数的平方处,并且不会停止筛选大于要筛选的顶部数的平方根的基本素数。由于多个嵌套的枚举操作,它也很慢。
它实际上是对 Linq Aggregate 方法的滥用,并没有有效地使用生成的两个 Linq Range 中的第一个。它可以成为一个优化的 Trial Division,枚举开销更少,如下所示:
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 答案快很多倍。但是,由于 List 生成和多个枚举以及多个除法(由模隐含)操作,它仍然很慢且内存密集。
以下真正的 Eratosthenes 实现的 Sieve 实现运行速度大约快 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) 上以大约 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: 但是,为了产生问题所问的少于一万万亿(十次方)的素数的枚举,需要使用多核处理的分段分页方法,但即使使用 C++ 和非常高度优化的 PrimeSieve,这将需要超过 400 小时才能产生找到的素数数量,并且需要数十倍的时间来枚举所有素数,因此需要一年多的时间才能完成问题所要求的事情。尝试使用未优化的试除法算法来做到这一点,即使使用优化的试除法算法,例如在十到百万分之二的幂年(即两百万零年!! !)。
难怪他的台式机在他尝试的时候只是坐着不动!!!!如果他尝试了一个更小的范围,比如一百万,他仍然会发现它在实施时需要几秒钟的范围。
我在这里发布的解决方案也不会削减它,因为即使是最后一个埃拉托色尼筛子,该范围内也需要大约 640 TB 的内存。
这就是为什么只有像 PrimeSieve 这样的页面分段方法才能在完全指定的范围内处理这类问题,甚至需要很长时间,比如几周到几年,除非你可以访问一台超级计算机数十万个核心。 END_EDIT_ADD