0

I am trying to make a recursive function in C that calculates the sum of digits in 2ⁿ, where n < 10⁷. I made something that works, but it's very slow (for n = 10⁵ it takes 19 seconds). The function must return the sum in maximum 1 second. My algorithm calculates 2ⁿ using arrays to store its digits and it's not using a recursive function.

Is there any way to compute this sum of digits without calculating 2ⁿ? Or a faster way to calculate 2ⁿ and its digits sum?

P.S.: The recursive function must get only the n parameter, i.e. int f(int n);

Late edit: I wrote a recursive solution; it is faster, but it doesn't work for n > 10⁵.

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

int sumOfDigits(int* num, int n) {
    if (n == 0) {
        int sum = 0;
        for (int i = 1; i <= num[0]; ++i) {
            while (num[i] > 0) {
                sum += num[i] % 10;
                num[i] /= 10;
            }
        }
        return sum;
    }

    int carry = 0;
    for (int i = 1; i <= num[0]; ++i) {
        num[i] = num[i] * 2 + carry;
        carry = num[i] / 1000000000;
        num[i] %= 1000000000;
        if (carry != 0 && i == num[0]) {
            ++num[0];
        }
    }

    return sumOfDigits(num, n - 1);
}

int main (void) {
    int n = 100000;
    int size = (n*log10(2) + 1) / 9 + 2;

    int* num = calloc(size, sizeof(int));
    num[0] = 1;
    num[1] = 1;
    printf("\n%d", sumOfDigits(num, n));
    free(num);
    return 0;
}

Toby Speight
  • 27,591
  • 48
  • 66
  • 103

2 Answers2

1

It seems that the posted code is using an "implicit" arbitrary precision type (with "digits" in the range [0, 999999999]) to recursively calculate all the multiplication by 2, which means, for e.g. n = 100, to perform 100 times those expansive calculation.

It should be more efficient (O(log(n)) instead of O(n)) to perform each time a multiplication of the number by itself or by 2, based on whether the exponent is even or odd. E.g. 27 = 2 * (23 * 23).

Another approach would be to explicitly implement a Bing Int type, but with a binary underlying type (say a uint32_t). It would be trivial to calculate 2n, it'd be just an array of zeroes with a final power of two (again, just one non-zero bit).

Now, to get the sum of the (base 10) digits you need to transform that number in base, say 100000000 (like the OP did), and to do that, you have to implement a long subtraction between two Big Ints and a long division by 100000000, which will give you the remainder too. Use that remainder to calculate the partial sum of the digits and iterate.

The following is a minimal implementation, testable here.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

#define D_BASE 1000000
#define MSB_MASK 1 << 31

typedef struct
{
    uint32_t size;
    uint32_t capacity;
    uint32_t *digits;
} BigInt;

void divide_bigint(BigInt *n, uint32_t x, uint32_t *remainder);

BigInt *make_bigint_of_two_raised_to(uint32_t n)
{
    BigInt *p = malloc(sizeof *p);
    if (!p)
    {
        perror("Fatal error");
        exit(1);
    }
    uint32_t pos = n / 32;
    uint32_t remainder = n % 32;
    uint32_t capacity = (remainder == 31) ? pos + 2 : pos + 1;
    uint32_t *pp = calloc(capacity, sizeof *pp);
    if (!pp)
    {
        perror("Error initializing a Big Int as a power of two");
        free(p);
        exit(1);       
    }
    p->capacity = capacity;
    p->size = capacity;
    pp[pos] = 1u << remainder;
    p->digits = pp;
    return p;
}

void free_bigint(BigInt **p);

uint64_t sum_of_digits_of_two_raised_to_the_power(uint32_t n)
{
    BigInt *power_of_two = make_bigint_of_two_raised_to(n);
    uint32_t remainder;
    uint64_t sum = 0;
    while (!(power_of_two->size == 1  &&  power_of_two->digits[0] == 0))
    {        
        divide_bigint(power_of_two, 1000000000, &remainder);
        while (remainder)
        {
            sum += remainder % 10;
            remainder /= 10;
        }
    }
    free_bigint(&power_of_two);
    return sum;
}

void test(uint32_t n)
{
    uint64_t sum = sum_of_digits_of_two_raised_to_the_power(n);
    printf("Sum of digits of 2^%d: %" PRIu64 "\n", n, sum);
}

int main(void)
{
    test(5);
    test(10);
    test(1000);
    test(10000);
    test(100000);
    test(1000000);
    return 0;
}

void shrink_size(BigInt *n)
{
    while ( n->size > 1 )
    {
        if ( n->digits[n->size - 1] == 0  &&  !(n->digits[n->size - 2] & MSB_MASK) )
            --n->size;
        else
            break;
    }
}

void divide_bigint(BigInt *n, uint32_t x, uint32_t *remainder)
{
    uint64_t carry = 0;
    uint32_t i = n->size;
    while ( i-- > 0 )
    {
        carry <<= 32;
        carry += n->digits[i];
        if ( carry < x )
        {
            n->digits[i] = 0;
            continue;
        }
        uint64_t multiplier = (carry / x);
        carry -= multiplier * x;
        n->digits[i] = (uint32_t)multiplier;
    }
    shrink_size(n);
    *remainder = carry;
}


void free_bigint(BigInt **p)
{
    if (p && *p)
    {
        free((*p)->digits);
        free(*p);
        *p = NULL;
    }
}
Bob__
  • 12,361
  • 3
  • 28
  • 42
  • You're right, but how should I do this for numbers represented as arrays? – Popescu Ștefan Dec 01 '19 at 13:06
  • @PopescuȘtefan At the moment you have one number which is constantly multiplied by a constant 2, you could write a function which implements a [long multiplication](https://en.wikipedia.org/wiki/Multiplication_algorithm#Long_multiplication) of *two* numbers (which happens to be the same). – Bob__ Dec 01 '19 at 13:15
  • I don't understand what does "a long subtraction between two Big Ints and a long division by 100000000" means. Can you give me an example? How should I subtract them? – Popescu Ștefan Dec 01 '19 at 14:35
  • It works, but for n = 10^6 it is too slower. The program must return requested value faster than 100 ms. – Popescu Ștefan Dec 02 '19 at 15:00
  • @PopescuȘtefan Sorry, If those are the exact requirements (the simple sum, not the one referred to in some of the comments and the timing for n=10^6 case), I'm afraid that some number theory trick, that I'm not aware of, would be required. – Bob__ Dec 02 '19 at 15:32
  • Actually, your solution is very good. I will try to improve it to get a faster response. But I have a question: Is "uint32_t" equal to "unsigned int"? – Popescu Ștefan Dec 02 '19 at 16:29
  • @PopescuȘtefan It depends. Those are fixed sixed types (if your implementation supports them), while the C Standard doesn't specify a size for `int`, only its minimal range. See e.g. [this](https://en.cppreference.com/w/c/types/integer) and [this](https://en.cppreference.com/w/c/language/arithmetic_types). – Bob__ Dec 02 '19 at 17:16
1

2^8 = (2 * 2 * 2 * 2 * 2 * 2 * 2 * 2) = (2 * 2 * 2 * 2) * (2 * 2 * 2 * 2) = (2 * 2 * 2 * 2)^2 = ((2 * 2) * (2 * 2))^2 = ((2 * 2)^2)^2 = ((2^2)^2)^2

So, first you need to calculate log(2, n), to see how you can calculate effectively. If log(2, n) is an integer, then you can simply calculate the square of the square of the ... of the square with very few operations. If log(2, n) is not an integer, then calculate 2^((int)log(2, n)) and thus you will very effectively do a partial calculation and then do the same for the remainder until there is no longer remainder.

Unify your partial results into a number (possibly represented by an array) and calculate the sum of the digits. Calculating the sum of the digits is straight-forward. The actual calculation of the 2^n is what takes the most time.

If you do not reach the limits of a number format, then you can think about shift left, but with the domain you work with this is not really an option.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • Ok. Can you give me an example of code that calculates the square of a number represented as array? – Popescu Ștefan Dec 01 '19 at 16:08
  • @PopescuȘtefan this example is C++, but the idea is similar in C: https://stackoverflow.com/questions/18357925/calculating-products-of-large-numbers-using-arrays – Lajos Arpad Dec 01 '19 at 16:38