0

I've come up with a very efficient algorithm for calculating prime numbers. It uses bit arithmetics ie AND , OR, XOR etc. and its based on the sieve of eratosthenes.

For numbers below 32 it works. For example when n = 31 I get the output:

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,

But when I use a bigger value such as 40 I get a different output:

2,

I am at a loss to explain why this is so, I need guidance.

Below is the code: https://github.com/rsgilbert/c/blob/master/chp2/sieve-bitset.c

#include <stdio.h>
#include <math.h>

#define MAXLINE 1000
#define A_1 1 // first number in GP 

// sieve of eratosthenes
// Using bit arithmetic
long btoi(char s[]);
void itob(long n, char s[]);
long runningPrimes(long max);
void printBits(long num);
long gpSum(long a_1, long r, long n);
long numElementsInGP(long a_1, long a_n, long r);
long lastElementInBitset(long width);
long width(long factor, long n);
long paddedSum(long sum, long n, long i);
long commonRatio(long i);
long flip(long bitset);
void sieve(long n, long primes[]);
long dropRightBits(long bitset, long noToDrop);
void primeBitsetToArray(long bitset, long primes[]);
void printPrimes(long primes[], size_t size);
long greaterFirstBit(long bitset);

long main()
{
    long n = 31; // 32 is not supported (??) I dont know why
    long runner = runningPrimes(n);
    long primes[n];
    sieve(n, primes);
    printPrimes(primes, n);
}


/**
 * sieve of eratosthenes
 * Algorithm for finding all prime numbers upto a given limit.
 * We go through natural numbers starting with 2 removing out multiples of each.
 */
void sieve(long n, long primes[])
{
    // 1. fill in numbers
    long runner = runningPrimes(n);
    long start_no = 2;
    long i = start_no;
    while(i <= n)
    {
        // printf("%d\n", i);
        long w = width(i, n);
        long lastEl = lastElementInBitset(w);
        long r = commonRatio(i);
        long numEls = numElementsInGP(A_1, lastEl, r);
        long sum = gpSum(A_1, r, numEls);
        //  printf("non padded sum: w %d , r %d , numEls %d ", w, r, numEls);
        // printBits(sum);
        sum = paddedSum(sum, n, i);  
        // We need to flip `sum` bits because currently 1s in sum represent multiples
        // If we flip 1010 it becomes 101. But notice we also want to remove 1000. 
        // If we dont handle this, 4 will show up in the primes
        // So we first get a copy of first bit 
        long grtrBitset = greaterFirstBit(sum);
        sum = flip(sum);
        sum = grtrBitset | sum;

        // 
        // printf("sum: gr %d ", grtrBitset);
        // printBits(grtrBitset);
        // printf("sum ");
        // printBits(sum);
        // Cancel out bits that represent multiples of i
        // We are going to drop some bits. The ones that are multiples of i
        // We first store some values
        long bitsOnRightToDrop = n - (2 * i) + 1;
        long notToChangeBits = dropRightBits(runner, bitsOnRightToDrop);
        long withoutSum = runner & sum;
        // printf("Runner: ");
        runner = notToChangeBits | withoutSum;
        // printBits(runner);
        i++;
    }
    primeBitsetToArray(runner, primes);
}

long runningPrimes(long max)
{
    return pow(2, max) - 1;
}

long paddedSum(long sum, long n, long i) {
    return sum * pow(2, (n % i));
}

/** Find sum of a geometric progression 
 * a_1: first element in GP
 * r: common ratio
 * n: number of elements in GP
*/
long gpSum(long a_1, long r, long n)
{
    return a_1 * (pow(r, n) - 1) / (r - 1);
}

/* Compute common ratio to be used for a given number */
long commonRatio(long i)
{
    return pow(2, i);
}

/** Find number of elements in a GP
 * a_1: first element in GP
 * a_n: last element in GP
 * r : common ratio
 */
long numElementsInGP(long a_1, long a_n, long r)
{
    return log2(a_n / a_1) / log2(r) + 1;
}

/* Find last element in bitset as decimal integer
* For example if bitset is 10010 , last element is 10000 = 16
* width: Number of characters in bitset.
*/
long lastElementInBitset(long width)
{
    return pow(2, (width - 1));
}

/* Produces the number of bits from first multiple greater than factor to last multiple less than n. */
long width(long factor, long n)
{
    long firstMultGR = 2 * factor;
    long lastMultLess = n - (n % factor);
    return lastMultLess - firstMultGR + 1;
}



// -- Bit functions --

/** Convert binary to decimal integer */
long btoi(char s[])
{
    long result = 0;
    long i = 0;
    while (s[i] != 0)
    {
        if (s[i] != '0' && s[i] != '1')
            return -1;
        result *= 2;
        result += s[i] - '0';
        i++;
    }
    return result;
}

/** convert decimal integer to binary */
void itob(long n, char s[])
{
    if (n == 0)
    {
        s[0] = '0';
        s[1] = 0;
        return;
    };
    if (n < 0)
    {
        s[0] = '-';
        s[1] = '1';
        s[2] = 0;
        return;
    }
    long pos = log2(n);
    s[pos + 1] = 0;
    while (pos != 0)
    {
        s[pos] = '0' + n % 2;
        n = n / 2;
        pos--;
    }
    // pos will be 0
    s[pos] = '1';
}

/**
 * Prints binary representation of set
 */
void printBits(long num)
{
    char result[MAXLINE];
    itob(num, result);
    printf("%s\n", result);
}

// Flip bits. For example 10110 becomes 1001
long flip(long bitset)
{
    long mask = pow(2, (long) log2(bitset) + 1) - 1;
    return bitset ^ mask;
}

// Drop some bits from the right side of a bitset. For example dropRighBits(btoi("1001100110"), 6) produces 1001000000
long dropRightBits(long bitset, long noToDrop) {
    long mask = pow(2, noToDrop) - 1; 
    long rightFlippedBitset = bitset ^ mask;
    return bitset & rightFlippedBitset;
}


/* Copy bitset representing prime positions into an array of prime numbers */
void primeBitsetToArray(long bitset, long primes[])
{
    char temp[MAXLINE];
    itob(bitset, temp);
    // printf("bitset %s\n", temp);
    // in the bitset, the first position represents number 1 then 2 ... etc So our primes will start at index 1 
    long i = 1;
    long j = 0;
    while(temp[i] != 0)
    {   
        if(temp[i] == '1')
        {            
            // in the bitset, the first position is 1 then 2 ... etc
            primes[j] = i + 1;
            // printf("%d\n", i + 1);
            j++;
        }
        i++;
    }
    primes[j] = -1;
}

/* Return bitset for the first bit in a given bitset.
* For example bitsetForFirstBit(10) = 8
*/
long bitsetForFirstBit(long bitset) {
    return pow(2, log2(bitset));
}

/* Return bitset that is greater than given bitset but also a multiple of 2.
* For example bitsetForFirstBit(10) = 16
*/
long greaterFirstBit(long bitset) {
    return pow(2, (long) log2(bitset) + 1);
}


void printPrimes(long primes[], size_t size)
{
    long i = 0;
    while(i < size && primes[i] != -1)
    {
        printf("%d, ", primes[i]);
        i++;
    }
    printf("\n");
}
Gilbert
  • 2,699
  • 28
  • 29
  • 4
    Please learn how to create a [mre] to show us, with emphasis on the ***minimal*** part. It will also make it much easier for you to debug your program. – Some programmer dude Mar 05 '22 at 08:29
  • 2
    @Gilbert And you call this approach efficient?! – Vlad from Moscow Mar 05 '22 at 08:30
  • 3
    Two things: Don't use the floating-point `pow` function for integer power operations. Especially for powers of two which can be easily created by simple bit-shifting. Secondly, `long` isn't guaranteed to be 64 bits or larger, it can still be 32 bits which means it can only hold values of around plus/minus two billion. – Some programmer dude Mar 05 '22 at 08:32
  • @Someprogrammerdude thanks for the info about not using `pow` function. I am just learning C so I didnt know. I'll try it out. About the long, the bits are in text form (string) so I never really use them as billion etc. As you see, the biggest number being supported is 31 which is way smaller than 1bn – Gilbert Mar 05 '22 at 08:36
  • @VladfromMoscow Yes, it runs in O(n) – Gilbert Mar 05 '22 at 08:37
  • 2
    Two to the power of 31 is 2147483648, which is one too much for a 32-bit signed integer (using two's complement). Two to the power of 32 is 4294967296, which doesn't fit in a 32-bit signed integer at all. Perhaps you can start to see the problem if your `long` is only 32 bits, and you attempt to use powers up to 40? Since you're not dealing with negative numbers, start by using an unsigned type. And to give you some better range, use `uint64_t` which is guaranteed to be 64 bits. – Some programmer dude Mar 05 '22 at 08:42
  • @Someprogrammerdude thanks very much. With uint64_t I can go upto n=53 but no further. I didnt know I was using such big numbers in the calculations. The gpSum function grows very rapidly! But I've learnt alot from writing the code so its been a good experience. Thanks once again. You can copy your comment into an answer and I'll mark it as closed – Gilbert Mar 05 '22 at 09:05
  • 2
    with your current horriblness `uint64_t` should go up to 63 ... if it goes only up to 53 it hints you have floating point `double` somewhere along the way (`pow` maybe?) ... anyway SoE can be done much better see [Prime numbers by Eratosthenes quicker sequential than concurrently?](https://stackoverflow.com/a/22477240/2521214) and inspect `void getprimes(int p)` function closely ... and even that can be reduced ... for example you do not need sqrt... its enough to half the bitwidth ... – Spektre Mar 05 '22 at 11:26

0 Answers0