【问题标题】:Sieve of Eratosthenes - bitwise optimization problem埃拉托色尼筛 - 按位优化问题
【发布时间】:2020-05-21 09:20:00
【问题描述】:

我想你们每个人都遇到过 Eratosthenes sieve 的按位运算优化代码。我试图绕开它,我对这个实现中的一个操作有疑问。以下是 GeeksforGeeks 的代码:

bool ifnotPrime(int prime[], int x) { 
    // checking whether the value of element 
    // is set or not. Using prime[x/64], we find 
    // the slot in prime array. To find the bit 
    // number, we divide x by 2 and take its mod 
    // with 32. 
    return (prime[x / 64] & (1 << ((x >> 1) & 31))); 
} 

// Marks x composite in prime[] 
bool makeComposite(int prime[], int x) { 
    // Set a bit corresponding to given element. 
    // Using prime[x/64], we find the slot in prime  
    // array. To find the bit number, we divide x 
    // by 2 and take its mod with 32. 
    prime[x / 64] |= (1 << ((x >> 1) & 31)); 
} 

// Prints all prime numbers smaller than n. 
void bitWiseSieve(int n) { 
    // Assuming that n takes 32 bits, we reduce 
    // size to n/64 from n/2. 
    int prime[n / 64]; 

    // Initializing values to 0 . 
    memset(prime, 0, sizeof(prime)); 

    // 2 is the only even prime so we can ignore that 
    // loop starts from 3 as we have used in sieve of 
    // Eratosthenes . 
    for (int i = 3; i * i <= n; i += 2) { 

        // If i is prime, mark all its multiples as 
        // composite 
        if (!ifnotPrime(prime, i)) 
            for (int j = i * i, k = i << 1; j < n; j += k) 
                makeComposite(prime, j); 
    } 

    // writing 2 separately 
    printf("2 "); 

    // Printing other primes 
    for (int i = 3; i <= n; i += 2) 
        if (!ifnotPrime(prime, i)) 
            printf("%d ", i); 
} 

// Driver code 
int main() { 
    int n = 30; 
    bitWiseSieve(n); 
    return 0; 
} 

所以我的问题是:

  1. (prime[x/64] &amp; (1 &lt;&lt; ((x &gt;&gt; 1) &amp; 31)) 更具体地说是(1 &lt;&lt; ((x &gt;&gt; 1) &amp; 31)); 是什么意思
  2. prime[x/64] 中,当我们使用 32 位整数时,为什么要除以 64 而不是 32
  3. 如果n &lt; 64int prime[n/64] 是否正确?

【问题讨论】:

  • 大概是因为筛子只代表奇数,不会在偶数上浪费空间。您可以轻松处理偶数。因此,您可以在 32 位中表示 64 个数字的范围。
  • OT: bool makeComposite(...){...} --> void makeComposite(...){...}
  • 这段代码可读性不强,看看this,它是C++,它被写成更具可读性。基本上算法是相同的,但信息不存储在位中(没有意义的情况下 constexpr)。将std::array 更改为std:vector,您就有了版本,其中标志存储在单个位中。
  • 关于问题 3:只有当 n 是 64 的倍数时它才是正确的(在 C 中,而不是在 C++ 中)。Geeksforgeeks 以不是特别好或不可靠而闻名。远离它可能是个好主意。
  • @molbdnilo 角落案例:int prime[n/64]; 在 C 中当 n==0 或小于 0 时无效,即使它是 64 的倍数。

标签: c bit-manipulation bitwise-operators bit-shift sieve-of-eratosthenes


【解决方案1】:

代码中存在多个问题:

  • makeComposite() 应该有返回类型 void
  • 如果x == 63 (1 &lt;&lt; ((x &gt;&gt; 1) &amp; 31)) 具有未定义的行为,因为1 &lt;&lt; 31 溢出了int 类型的范围。您应该使用1U 或最好使用1UL 来确保32 位。
  • 移动表项并屏蔽最后一位会更简单:return (prime[x / 64] &gt;&gt; ((x &gt;&gt; 1) &amp; 31)) &amp; 1;
  • 不要假设类型int 有32 位,而应该使用uint32_t 作为位数组的类型。
  • 如果n 不是64 的倍数,则数组int prime[n / 64]; 太短。请改用uint32_t prime[n / 64 + 1];。这是您的示例n = 30 的问题,因此创建的数组长度为0
  • ifnotPrime(n) 仅返回奇数值的有效结果。更改此函数并将其命名为 isOddPrime() 可能会更好、更易读。

关于您的问题:

(prime[x/64] &amp; (1 &lt;&lt; ((x &gt;&gt; 1) &amp; 31)) 更具体地说是(1 &lt;&lt; ((x &gt;&gt; 1) &amp; 31)) 是什么意思?

x 首先除以 2(右移一位),因为数组中只有奇数有一个位,然后将结果用 31 屏蔽,以保持 5 个低位作为字中的位数。对于任何无符号值xx &amp; 31 等价于x % 32x / 64 是测试该位的字号。

如上所述,1int,因此不应向左移动 31 个位置。使用1UL 可确保该类型至少有 32 位,并且可以移动 31 个位置。

prime[x/64] 中,当我们使用 32 位整数时,为什么要除以 64 而不是 32

数组中的位对应于奇数,因此一个 32 位的字包含 64 个数字的素数信息:32 个已知为合数的偶数和 32 个奇数,如果该数字为合成的。

如果n &lt; 64int prime[n/64] 是否正确?

不是不是,如果n不是64的倍数是不正确的:大小表达式应该是(n + 63) / 64,或者更好的是int prime[n/64 + 1]


这是一个修改版本,您可以在其中传递命令行参数:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

bool isOddPrime(const uint32_t prime[], unsigned x) {
    // checking whether the value of element
    // is set or not. Using prime[x/64], we find
    // the slot in prime array. To find the bit
    // number, we divide x by 2 and take its mod
    // with 32.
    return 1 ^ ((prime[x / 64] >> ((x >> 1) & 31)) & 1);
}

// Marks x composite in prime[]
void makeComposite(uint32_t prime[], unsigned x) {
    // Set a bit corresponding to given element.
    // Using prime[x/64], we find the slot in prime
    // array. To find the bit number, we divide x
    // by 2 and take its mod with 32.
    prime[x / 64] |= (1UL << ((x >> 1) & 31));
}

// Prints all prime numbers smaller than n.
void bitWiseSieve(unsigned n) {
    // Assuming that n takes 32 bits, we reduce
    // size to n/64 from n/2.
    uint32_t prime[n / 64 + 1];

    // Initializing values to 0 .
    memset(prime, 0, sizeof(prime));

    // 2 is the only even prime so we can ignore that
    // loop starts from 3 as we have used in sieve of
    // Eratosthenes .
    for (unsigned i = 3; i * i <= n; i += 2) {
        // If i is prime, mark all its multiples as composite
        if (isOddPrime(prime, i)) {
            for (unsigned j = i * i, k = i << 1; j < n; j += k)
                makeComposite(prime, j);
        }
    }

    // writing 2 separately
    if (n >= 2)
        printf("2\n");

    // Printing other primes
    for (unsigned i = 3; i <= n; i += 2) {
        if (isOddPrime(prime, i))
            printf("%u\n", i);
    }
}

// Driver code
int main(int argc, char *argv[]) {
    unsigned n = argc > 1 ? strtol(argv[1], NULL, 0) : 1000;
    bitWiseSieve(n);
    return 0;
}

【讨论】:

  • 角:for (unsigned i = 3; i * i &lt;= n; i += 2)i*i 溢出而导致UINT_MAX 附近的大素数n 失败。推荐for (unsigned i = 3; i &lt;= n/i; i += 2)
  • @chux-ReinstateMonica:如果n 太接近UINT_MAX,则i * i 溢出的极端情况:是的,这是一个很好的观点,但仅适用于非常大的n 值无论如何都会导致int32_t prime[(n + 63) / 64]; 失败。
  • 我把它改成了n / 64 + 1,这对这个案例来说已经足够了。
  • 我同意你的目标,但我更喜欢不使用除法的解决方案。使用快速收敛方法计算n 的平方根可能是最好的。
  • @chux-ReinstateMonica: ifnotPrime 确实值得一提:)
【解决方案2】:

1)x%32 等价于x&amp;31:逻辑且在最低有效 5 位上。所以基本上((x&gt;&gt;1)&amp;31) 暗示((x/2)%32)。而1&lt;&lt;x 表示2^x 所以你要问的是2^((x/2)%32)

2) 实现中的一个优化是,它完全跳过了所有偶数。

3)n可以小于64

【讨论】:

  • 我不明白 q(3 的答案
  • n 小于您尝试查找所有质数的数字,如果它小于64,它将在 O(1) 时间内完成,而无需使用按位优化。跨度>
  • Detail : "x%32 is equivalent to x&31" 当x &gt;= 0被OP使用时为真,但在x &lt; 0时不一样
  • 如果n &lt; 64int prime[n / 64];无效。
  • int prime[n/64] 相当于int prime[0] if n&lt;64 这是有效的声明,尽管它不用于计算将通过蛮力为n&lt;64 计算的素数,在我的回答中我写了n can be less than 64 这是真的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-16
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多