填坑了。
之前说到,2 和 3之后,所有质数都必须满足6 n ± 1这个条件。所以利用这原理,可以提升三倍性能。这公式怎么来的呢?
首先我们知道,2以后的质数都必须是奇数,公式表达就是2n + 1,当然用2n - 1也行。
我们要把能整除3的过滤掉。即 2n + 1 不能整除3。我们做适当转换:
2n + 1 => 3n - n + 1
可见,我们只要确保1 - n不能整除3就可以。
令 n = 3 m + x,则有2 * (3m + x) + 1 => 6m + 2x + 1 ,x 的取值范围为 0 和 2。
所以得到,6m + 1 或者 6m + 5,其中6m + 5 等同于 6m - 1,只是m取值范围不同。
证明完毕。
同样,当我们有了6n ± 1之后,怎么去除 5和7 的倍数呢?刚好就这么巧:
6n ± 1 => 5n + n ± 1
6n ± 1 => 7n - n ± 1
懂了吧,剩下展开就行了。实测下,把3倍数过滤掉,用欧拉确实获得巨大的性能提升。
再把5倍数过滤掉,还能提升一点。到再过滤7倍数的时候,优势就有点看不到了,原因是,5级过滤到7级过滤,计算总量是从8/30 降到 48/210,节约率相比原始来说才3.8%,收益不多。
说好的代码呢?呃,上吧,F# 的代码。
let getPrimers maxValue =
let table = [|0uy; 2uy; 6uy; 8uy; 12uy; 18uy; 20uy; 26uy; 30uy; 32uy; 36uy; 42uy;
48uy; 50uy; 56uy; 60uy; 62uy; 68uy; 72uy; 78uy; 86uy; 90uy; 92uy;
96uy; 98uy; 102uy; 110uy; 116uy; 120uy; 126uy; 128uy; 132uy; 138uy;
140uy; 146uy; 152uy; 156uy; 158uy; 162uy; 168uy; 170uy; 176uy; 180uy;
182uy; 186uy; 188uy; 198uy; 200uy|]
let check = Array.zeroCreate (maxValue + 1)
let prime = Array.zeroCreate (maxValue / 210 * 48 + 48)
prime.[0]
prime.[1]
prime.[2]
prime.[3]
let rec loop n count step =
let i = n + int32 table.[step]
if i <= maxValue then
let count =
if not check.[i] then
prime.[count]
count + 1
else
count
let rec loopPrime j =
if j < count then
let comp = i * prime.[j]
if comp <= maxValue then
check.[comp] 0 then
loopPrime (j + 1)
loopPrime 4 // 把0 改为 4,这一步很重要,因为原理上杜绝了 2357倍数的检查, // 所以直接不用标注, 2000万数据测试下,节约了1/4时间 if step + 1 = table.Length then
loop (n + 210) count 0
else
loop n count (step + 1)
loop 11 4 0
===================================
看完一圈,没有一个好打的。
就抄个公式出来了事。那些所谓擅长数学的,怎么不知道2和3之后,所有质数都必须满足6 n ± 1这个条件?因为只有6 n ± 1才能避开2和3的倍数。 欧拉筛法改一下,速度一下子提到3倍了。即使你真的不懂,也该知道2之后,所有质数都是奇数吧?也能从原来提升两倍速度了。如果你想,数组也可以趁机把数组开小一点。优化都没尝试过就说这个算法内存占用什么的。。。。。
不才还有个「210里面中检测48个可能位置的」,也就是说,从开始就排除2、3、5、7的倍数。大概能提升到4倍(略低于4倍)。原理和代码稍晚放出。