由于没有任何人接受“Sundaram vs. Eratosthenes”的问题,所以我坐下来分析了它。结果:经典的 Sundaram's 的透支率高于仅赔率的 Eratosthenes;如果你应用一个明显的、小的优化,那么透支是完全一样的——原因很明显。如果你修复 Sundaram's 以避免完全透支,那么你会得到类似Pritchard's Sieve 的东西,它要复杂得多。
Lucky's Notes 中对 Sundaram 筛子的阐述可能是迄今为止最好的;稍微重写以使用假设的(即非标准且未在此处提供)类型bitmap_t
,它看起来有点像这样。为了测量过度绘制,位图类型需要对应于BTS
(位测试和设置)CPU 指令的操作,该指令可通过 Wintel 编译器和 gcc 的 MinGW 版本的 _bittestandset() 内部函数获得。内在函数对性能非常不利,但对于计算透支非常方便。
注意:对于筛选所有素数最多为 N 的人将调用具有 max_bit = N/2 的筛子;如果结果位图的位 i 被设置,则数字 (2 * i + 1) 是复合数。该函数的名称中包含“31”,因为对于大于 2^31 的位图,索引数学中断;因此此代码只能筛选最多 2^32-1 的数字(对应于 max_bit <= 2^31-1)。
uint64_t Sundaram31_a (bitmap_t &bm, uint32_t max_bit)
{
assert( max_bit <= UINT32_MAX / 2 );
uint32_t m = max_bit;
uint64_t overdraw = 0;
bm.set_all(0);
for (uint32_t i = 1; i < m / 2; ++i)
{
for (uint32_t j = i; j <= (m - i) / (2 * i + 1); ++j)
{
uint32_t k = i + j + 2 * i * j;
overdraw += bm.bts(k);
}
}
return overdraw;
}
Lucky 的绑定j
是准确的,但绑定i
非常松散。收紧它并丢失m
我添加的别名以使代码看起来更像网络上的常见说明,我们得到:
uint64_t Sundaram31_b (bitmap_t &bm, uint32_t max_bit)
{
uint32_t i_max = uint32_t(std::sqrt(double(2 * max_bit + 1)) - 1) / 2;
uint64_t overdraw = 0;
bm.set_all(0);
for (uint32_t i = 1; i <= i_max; ++i)
{
for (uint32_t j = i; j <= (max_bit - i) / (2 * i + 1); ++j)
{
uint32_t k = i + j + 2 * i * j;
overdraw += bm.bts(k);
}
}
return overdraw;
}
assert
为了减少噪音而倾倒了它,但它实际上仍然有效且必要。现在是时候降低强度了,将乘法转换为迭代加法:
uint64_t Sundaram31_c (bitmap_t &bm, uint32_t max_bit)
{
uint32_t i_max = uint32_t(std::sqrt(double(2 * max_bit + 1)) - 1) / 2;
uint64_t overdraw = 0;
bm.set_all(0);
for (uint32_t i = 1; i <= i_max; ++i)
{
uint32_t n = 2 * i + 1;
uint32_t k = n * i + i; // <= max_bit because that's how we computed i_max
uint32_t j_max = (max_bit - i) / n;
for (uint32_t j = i; j <= j_max; ++j, k += n)
{
overdraw += bm.bts(k);
}
}
return overdraw;
}
将循环条件转换为使用k
让我们输了j
;事情现在应该看起来非常熟悉......
uint64_t Sundaram31_d (bitmap_t &bm, uint32_t max_bit)
{
uint32_t i_max = uint32_t(std::sqrt(double(2 * max_bit + 1)) - 1) / 2;
uint64_t overdraw = 0;
bm.set_all(0);
for (uint32_t i = 1; i <= i_max; ++i)
{
uint32_t n = 2 * i + 1;
uint32_t k = n * i + i; // <= max_bit because that's how we computed i_max
for ( ; k <= max_bit; k += n)
{
overdraw += bm.bts(k);
}
}
return overdraw;
}
事情看起来像他们做的那样,是时候分析数学是否有理由证明某个明显的小变化。证明留给读者作为练习......
uint64_t Sundaram31_e (bitmap_t &bm, uint32_t max_bit)
{
uint32_t i_max = unsigned(std::sqrt(double(2 * max_bit + 1)) - 1) / 2;
uint64_t overdraw = 0;
bm.set_all(0);
for (uint32_t i = 1; i <= i_max; ++i)
{
if (bm.bt(i)) continue;
uint32_t n = 2 * i + 1;
uint32_t k = n * i + i; // <= m because we computed i_max to get this bound
for ( ; k <= max_bit; k += n)
{
overdraw += bm.bts(k);
}
}
return overdraw;
}
唯一与经典的仅赔率 Eratosthenes(除了名称)不同的是 的初始值k
,通常(n * n) / 2
用于古希腊语。然而,替代2 * i + 1
差异n
结果是 1/2,四舍五入为 0。因此,Sundaram是仅赔率的 Eratosthenes,没有跳过复合材料的“优化”以避免至少一些已经划掉的数字的划线。的值i_max
与希腊的 相同max_factor_bit
,只是使用完全不同的逻辑步骤得出,并使用略有不同的公式计算。
PS:在overdraw
代码中看到这么多次之后,人们可能想知道它实际上是什么......筛选数字高达 2^32-1(即完整的 2^31 位图) Sundaram 的透支为8,643,678,027(大约 2 * 2^32)或444.6%;通过将其变为仅赔率的小修复 Eratosthenes,透支变为 2,610,022,328 或 134.2%。