3

I guess every one of you has encountered the optimization code of Eratosthenes sieve with bitwise operations. I am trying to wrap my head around it and I have a question on one of the operations in this implementation. Here is the code from 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; 
} 

So my questions are:

  1. what is the meaning of (prime[x/64] & (1 << ((x >> 1) & 31)) and more specifically (1 << ((x >> 1) & 31));
  2. in prime[x/64] why do we divide by 64 and not with 32, when we work with 32 bit integer;
  3. Is int prime[n/64] correct if n < 64?
chqrlie
  • 131,814
  • 10
  • 121
  • 189
AVA
  • 75
  • 4
  • 1
    Presumably because the sieve is representing only odd numbers and does not waste space on the even numbers. You can deal with even numbers trivially. Therefore you can represent a range of 64 numbers in 32 bits. – Weather Vane May 21 '20 at 09:22
  • OT: `bool makeComposite(...){...}` --> `void makeComposite(...){...}` – David Ranieri May 21 '20 at 09:27
  • This code is not very readable, take a look on [this](https://wandbox.org/permlink/ODZ2TL4VgjJv6b0c) which is C++ and it is written to be more readable. Basically algorithm is same, but information is not stored in bits (there is no point in case constexpr). Change `std::array` to `std:vector` and you have version, where flags are stored in single bits. – Marek R May 21 '20 at 09:35
  • 1
    Regarding question 3: it's only correct (in C, not in C++) if n is a multiple of 64. Geeksforgeeks is well known for not being particularly good or reliable. Staying away from it is probably a good idea. – molbdnilo May 21 '20 at 10:01
  • @molbdnilo Corner case: `int prime[n/64];` invalid in C when `n==0` or less than 0, even though it is a multiple of 64. – chux - Reinstate Monica May 29 '20 at 03:09

2 Answers2

3

There are multiple problems in the code:

  • makeComposite() should have return type void.
  • (1 << ((x >> 1) & 31)) has undefined behavior if x == 63 because 1 << 31 overflows the range of type int. You should use 1U or preferably 1UL to ensure 32 bits.
  • it would be simpler to shift the table entry and mask the last bit: return (prime[x / 64] >> ((x >> 1) & 31)) & 1;
  • instead of assuming that type int has 32 bits, you should use uint32_t as the type for the bit array.
  • the array int prime[n / 64]; is too short if n is not a multiple of 64. Use uint32_t prime[n / 64 + 1]; instead. This is a problem for your example as n = 30, so the array is created with a length of 0.
  • ifnotPrime(n) only returns a valid result for odd values. It might be better and more readable to change this function and name it isOddPrime().

Regarding your questions:

what is the meaning of (prime[x/64] & (1 << ((x >> 1) & 31)) and more specifically (1 << ((x >> 1) & 31))?

x is first divided by 2 (shift right one position) because only odd numbers have a bit in the array, then the result is masked with 31 to keep the 5 low order bits as the bit number in the word. For any unsigned value x, x & 31 is equivalent to x % 32. x / 64 is the word number where to test this bit.

As mentioned above, 1 is an int and thus should not be shifted left by 31 positions. Using 1UL ensures that the type has at least 32 bits and can be shifted by 31 positions.

in prime[x/64] why do we divide by 64 and not with 32, when we work with 32-bit integer?

The bits in the array correspond to odd numbers, so a 32-bit word contains primeness information for 64 numbers: 32 even numbers that are known to be composite and 32 odd numbers for which the corresponding bit it set if the number is composite.

Is int prime[n/64] correct if n < 64?

Not it is not, it is incorrect if n is not a multiple of 64: the size expression should be (n + 63) / 64, or better int prime[n/64 + 1]


Here is a modified version, where you can pass a command line argument:

#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;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Corner: `for (unsigned i = 3; i * i <= n; i += 2)` fails for large prime `n` near `UINT_MAX` due to `i*i` overflow. Recommend `for (unsigned i = 3; i <= n/i; i += 2)` – chux - Reinstate Monica May 28 '20 at 22:27
  • @chux-ReinstateMonica: corner case where `i * i` overflows if `n` is too close to `UINT_MAX`: yes, that's a good point, but only for very large values of `n` which would cause `int32_t prime[(n + 63) / 64];` to fail anyway. – chqrlie May 28 '20 at 22:40
  • 1
    I changed that to `n / 64 + 1`, which is good enough for this case. – chqrlie May 28 '20 at 22:42
  • I agree with you on the goal, yet I would prefer a solution that does not use division. Computing the square root of `n` with a fast converging method might be best. – chqrlie May 28 '20 at 22:46
  • 1
    @chux-ReinstateMonica: `ifnotPrime` deserved a note indeed :) – chqrlie May 28 '20 at 23:01
  • Seeking a full range alternative for `for (unsigned i = 3; i * i <= n; i += 2) {` without a `/` might make for a nice question. Your `isqrt(n)` idea is a good one. – chux - Reinstate Monica May 29 '20 at 03:02
1

1)x%32 is equivalent to x&31: a logical and on the least significant 5 bits. so basically ((x>>1)&31) implies ((x/2)%32). and 1<<x means 2^x so what you are asking is 2^((x/2)%32).

2)One optimization in implementation is, it skipped all even numbers altogether.

3)n can less than 64

Nurav
  • 167
  • 1
  • 3
  • 15
  • i dont understand the answer of q(3 – AVA May 21 '20 at 10:11
  • `n` is number less than which you are trying to find all the prime numbers if it is less than `64` it will do it in O(1) time without using bitwise optimisation. – Nurav May 21 '20 at 10:17
  • Detail : "x%32 is equivalent to x&31" is true when `x >= 0` as used by OP, yet is not the same when `x < 0` – chux - Reinstate Monica May 28 '20 at 22:33
  • If `n < 64`, `int prime[n / 64];` is invalid. – chux - Reinstate Monica May 29 '20 at 03:12
  • `int prime[n/64]` is equivalent to `int prime[0]` if `n<64` which is valid statement although it is not used for computation of primes that will be calculated via brute force for `n<64` and in my answer i have written `n can be less than 64` which is true – Nurav May 29 '20 at 03:33