Search code examples
cgcclong-integerlong-doublelong-long

Need large integers and floats. Trouble with long long int and long double


I am a sort of newbie to programming (actually coming back to it after a gap of 20 yrs :) ). I am trying to make a program for a question on project euler, to find sum of all prime numbers under 2 million. I am trying to use the Eratosthenes sieve algorithm. I am using code::blocks with GNU GCC compiler.

 #include <stdio.h>
 #define L 2000000
 int main()
 {
     unsigned long long int i, j, n, num[L], sum = 0;
     for (i = 0; i < L; i++)
         num[i] = i + 1;

     for (j = 2; j*j <=L; j++)
         {
             for (n = j; n*j<=L; n++)
                 {
                     num[n * j - 1] = 0;
                 }
         }
     for (i = 1; i < L; i++)
         {
             if (num[i] != 0)
                 {
                     printf("%llu\n",num[i]);
                     sum = sum + num[i];
                 }
         }
     printf("%llu\n", sum);
     return 0;
 }

The program works for all values of L below 1000000, but stops working above that. Also I get warning about the long long int as follows:

***||=== Build file: Debug in 2MILLIONPRIMESUM (compiler: GNU GCC Compiler) ===|

***E:\Code Blocks Projects\2MILLIONPRIMESUM\main.c||In function 'main':|

E:\Code Blocks Projects\2MILLIONPRIMESUM\main.c|22|warning: unknown conversion type character 'l' in format [-Wformat=]|

E:\Code Blocks Projects\2MILLIONPRIMESUM\main.c|22|warning: too many arguments for format [-Wformat-extra-args]|******

I get similar warnings when trying to use "long double" etc. Is this a problem with the compiler, that it cannot handle long long and long double?

The above code works without any warning (only for L <=1000000) on GDB online compiler. Request everyone to advise me on the above issues. Thank you so much in advance.


Solution

  • As pointed out in comments, 2000000 * sizeof(long long) is too large of an array to fit on the stack; confidence is high that this is the root cause of the issue.

    That said, the code works nicely when we add the dynamic memory allocation of num as shown below in the line having malloc(). We also added code to ensure the memory allocation was successful. If not, a printf() is there to alert the user, and then the program exits harmlessly.

    This code was compiled and run/checked by me before posting.

    EDIT: I ran one more test just now with sum as type double -- the final answer is still the same: 142913828922. This tends to support the notion that the code is working per design and requirement.

    #include <stdio.h>
    #include <stdlib.h>
    
    #define L 2000000
    
    int main()
    {
        unsigned long long int i, j, n, sum = 0;
        unsigned long long int *num = malloc( L * sizeof(unsigned long long int));
    
        if(!num)
        {
            printf("malloc() failed!\n");
            return 0;
        }
    
        for (i = 0; i < L; i++)
            num[i] = i + 1;
    
        for (j = 2; j*j <=L; j++)
        {
            for (n = j; n*j<=L; n++)
            {
                num[n * j - 1] = 0;
            }
        }
        for (i = 1; i < L; i++)
        {
            if (num[i] != 0)
            {
                printf("%llu\n",num[i]);
                sum = sum + num[i];
            }
        }
        printf("%llu\n", sum);
    
        /* As you malloc(), so shall you free()! (thank you Fe2O3): */
        free(num);
        return 0;
    }
    

    Output:

    ...
    ...
    1999733
    1999771
    1999799
    1999817
    1999819
    1999853
    1999859
    1999867
    1999871
    1999889
    1999891
    1999957
    1999969
    1999979
    1999993
    142913828922
    

    This 2nd version may interest you: per discussion with Fe2O3 in comments, it was revealed that the num array need not occupy a long long data type; that the range of num array will fit neatly into a 32-bit int.

    Furthermore, upon reflection, it was realized that such a change invites a considerable space savings. So much so that perhaps the adapted num array may now fit on the stack!

    This notion seems to be born out per the runnable code here.

    Conspicuously absent from the code is any hint of malloc(), and yet we're getting competent results per the featured Output:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define L 2000000
    
    int main()
    {
        unsigned long long int sum = 0;
        unsigned int i, j, n, num[L]; 
    
        for (i = 0; i < L; i++)
            num[i] = i + 1;
    
        for (j = 2; j*j <=L; j++)
        {
            for (n = j; n*j<=L; n++)
            {
                num[n * j - 1] = 0;
            }
        }
        for (i = 1; i < L; i++)
        {
            if (num[i] != 0)
            {
                if(i > L-200) /* just print last XX of array*/
                    printf("%llu\n",num[i]);
                sum = sum + num[i];
            }
        }
        printf("%llu\n", sum);
        printf("sizeof(int): %u\nsizeof(num): %u\n", sizeof(int), sizeof(num));
    
        return 0;
    }
    

    Output:

    1999817
    1999819
    1999853
    1999859
    1999867
    1999871
    1999889
    1999891
    1999957
    1999969
    1999979
    1999993
    142913828922
    sizeof(int): 4
    sizeof(num): 8000000
    

    Nice thing about Stackverflow community is that they'll bring ideas to the table that challenge and grow you. Both Fe2O3 and Jonathan Leffler mentioned using bits for even more space savings. (Wut?) At last I've worked it out...

    Here is version 3.0 for the world -- only puts about 240K on the stack -- nice! That's a long way from the 2 terrabytes we started with. (j/k -- was actually 2M * 64bits = 15.25MB)

    Runnable code is here.

    #include <stdio.h>
    #include <string.h> /* memset */
    
    #define L 2000000
    
    /* These one-line bit ops are a compromise between a macro and a function */
    static void clear_bit(unsigned char *x, int bitNum) { *x &= ~(1L << bitNum); }
    static int test_bit(unsigned char x, int bitNum) { return !!(x & (1L << (bitNum))); }
    static void set_bit(unsigned char *x, int bitNum) { *x |= (1L << bitNum); } /* unused */
    
    int main()
    {
        unsigned long i=8, j, n;
        unsigned char bits[L/8];
        unsigned long long int sum = 0;
    
        /* Set all the bits of the array to 1.  Replaces step in old code
        * where we filled the array with num = index + 1 */
        memset(bits, 255, sizeof(bits));
    
        /* This is the section where we *disqualify* non-prime entries by 
        * clearing the related bit.  Formerly we set the index to zero.*/
        for (j = 2; j*j <= L; j++)
        {
            for (n = j; n*j <= L; n++)
            {
                long num = n * j;
                clear_bit(&bits[num/8], num%8);
            }
        }
        /* In this section we'll do a print out and get the sum as before... */
        for (i = 2; i < L; i++)
        {
            if (test_bit(bits[i/8], (i%8))  != 0)
            {
                if(i > L-200) /* just print last XX of array*/
                    printf("%lu\n", i );
                sum += i;
            }
        }
        /*should say 142913828922  (142,913,828,922)*/
        printf("%lld\n", sum);
        return 0;
    }
    

    Output:

    1999817
    1999819
    1999853
    1999859
    1999867
    1999871
    1999889
    1999891
    1999957
    1999969
    1999979
    1999993
    142913828922
    

    Here is version 4.0 -- only puts about 122K on the stack.
    The bit array is halved since v3.0. Thanks again to the SO community for the inspiration -- please see comments for more, and the algo optimizations from Fe2O3.

    Runnable code is here.

    #include <stdio.h>
    #include <string.h> /* memset */
    
    #define L 2000000
    
    static void clear_bit(unsigned char *x, int bitNum) { *x &= ~(1L << bitNum); }
    static int test_bit(unsigned char x, int bitNum) { return !!(x & (1L << (bitNum))); }
    
    int main()
    {
        unsigned long i, j, n;
        unsigned char bits[L/16];    /* Only 125,000 bytes! */
        unsigned long long sum = 0;
    
        /* Set all the bits of the array to 1. */
        memset(bits, 255, sizeof(bits));
    
        /* Disqualify non-prime entries by clearing the related bit.  
        * Small note on how bit array is accessed:
         In an odd-numbers-only bit buffer, addressing is:
            bits[0] represents numbers: 1, 3, 5, 7, 9, 11,13,15
            bits[1] represents numbers: 17,19,21,23,25,27,29,31
            bits[2] represents numbers: 33,35,37,39,41,43,45,47 
            ...
            Hence, the math is: 
            bit array offset   = number/16;
            Representative bit = (number%16)/2
        */
        for (j = 2; j*j <= L; j++)
        {
            for (n = j; n*j <= L; n++)
            {
                long num = n * j; 
                long offset = num/16;
                if(num%2) /* if odd */
                    clear_bit(&bits[offset], (num%16)>>1 );
            }
        }
        /* Print out and sum: 2 will be a special case because it is the only
        * even prime number in the universe. Per our algo, we store no even 
        * numbers.  Hence, 2 (and its printing, if desired) is done manually. */
        sum = 2;  /* Primes start here */
        for(i = 3; i < L; i+=2) /* Now process the remaining primes... */
        {
            long offset = i/16;
            if(test_bit(bits[offset], (i%16)>>1 ) != 0)
            {
                if(i > L-200) /* just print last XX of array*/
                {
                    printf("%lu\n", i );
                }
                sum += i;
            }
        }
        /*should say 142913828922  (142,913,828,922). */
        printf("%llu\n", sum);
        return 0;
    }
    

    Output:

    1999817
    1999819
    1999853
    1999859
    1999867
    1999871
    1999889
    1999891
    1999957
    1999969
    1999979
    1999993
    142913828922
    

    My 2 cents for version 5.0:

    • accept command line argument for L, defaults to 2000000
    • only iterate over odd multiples of odd numbers (40% less time)
    • use unsigned long for all computations
    • bits array now allocated and initialized to 0, indicates composite numbers
    • simpler bit set and test functions.

    Chqrlie.

    #include <stdio.h>
    #include <stdlib.h>
    
    static void set_bit(unsigned char *x, int bitNum) { *x |= 1 << bitNum; }
    static int test_bit(unsigned char x, int bitNum) { return (x >> bitNum) & 1; }
    
    int main(int argc, char *argv[])
    {
        unsigned long L = argc > 1 ? strtol(argv[1], NULL, 0) : 2000000;
        unsigned long i, j, n;
        unsigned char *bits = calloc(1, L / 16 + 1);    /* Only 125,000 bytes! */
        unsigned long long sum;
    
        if (!bits) {
            fprintf(stderr, "out of memory\n");
            return 1;
        }
        /* Disqualify non-prime entries by clearing the related bit.
         * Small note on how bit array is accessed:
           in an odd-numbers-only bit buffer, addressing is:
           bits[0] represents numbers: 1, 3, 5, 7, 9, 11,13,15
           bits[1] represents numbers: 17,19,21,23,25,27,29,31
           bits[2] represents numbers: 33,35,37,39,41,43,45,47
           ...
           Hence, the math is:
           bit array offset   = number / 16;
           representative bit = (number % 16) / 2
         */
        for (j = 3; j * j <= L; j += 2)
        {
            for (n = j * j; n <= L; n += 2 * j)
            {
                unsigned long offset = n / 16;
                set_bit(&bits[offset], (n % 16) >> 1);
            }
        }
        /* Print out and sum: 2 will be a special case because it is the only
         * even prime number in the universe. Per our algo, we store no even
         * numbers.  Hence, 2 (and its printing, if desired) is done manually. */
        sum = 2;  /* Primes start here */
        for (i = 3; i <= L; i += 2) /* Now process the remaining primes... */
        {
            unsigned long offset = i / 16;
            if (!test_bit(bits[offset], (i % 16) >> 1))
            {
                sum += i;
                if (i > L - 200) /* just print last XX of array*/
                {
                    printf("%lu\n", i);
                }
            }
        }
        /*should say 142913828922  (142,913,828,922). */
        printf("%llu\n", sum);
        free(bits);
        return 0;
    }
    

    Output (13ms total time):

    1999817
    1999819
    1999853
    1999859
    1999867
    1999871
    1999889
    1999891
    1999957
    1999969
    1999979
    1999993
    142913828922