2

So i recently did a university exam and one of the questions asked us to create a program that would print out the nth number in the tribonacci sequence (1,1,1,3,5,9,17,31...). These numbers were said to go as large as 1500 digits long. I created a recursive function that worked for the first 37 tribonacci numbers. But a stack overflow occurred at the 38th number. The question had warned us about this and said that we would somehow need to overcome this, but i have no idea how. Were we meant to create our own data type?

double tribonacci(int n){
    if(n < 4){
        return 1;
    }else{
        return tribonacci(n-3) + tribonacci(n-2) + tribonacci(n-1);
    }
}

int main(int argc, char *argv[]){
    double value = tribonacci(atoi(argv[1]));
    printf("%lf\n", value);
}

This is the solution i wrote under exam conditions, which was within 15 minutes.

The program took the value of n from an input in the command line. We were not allowed to use any libraries except for stdlib.h and stdio.h. So with all that said, how might one create a data type large enough to print out numbers with 1500 digits (since the double data type only holds enough for up until the 37th tribonacci number)? Or is there another method to this question?

Roark
  • 69
  • 6
  • 3
    `double` is a really bad idea for fibonacci numbers. It has a very limited absolute precision. And we are not a tutoring service. What have you tried? Where **specifically** did you fail? Part of programming is to find solutions and that' definitively part of your homework. – too honest for this site Jun 19 '17 at 12:30
  • 1
    This is too broad. There *are* libraries handling large numbers, see [GMP](https://gmplib.org/) –  Jun 19 '17 at 12:31
  • 1
    @Olaf, typically I'm happy with 53 bits of precision. Enough to measure the distance from here to Pluto to the nearest cm. – Bathsheba Jun 19 '17 at 12:31
  • Yeah i know double is a really bad idea but we were only limited to the standard c library in the exam, so thats the best that could be done. – Roark Jun 19 '17 at 12:31
  • @Bathsheba: Did we read the same question? OP clearly states about 1500 digits. It all depends on the problem. Which precision do you need for the distance? – too honest for this site Jun 19 '17 at 12:32
  • @Roark no it's not, but rolling your own big number type might be a bit much work for an exam. –  Jun 19 '17 at 12:32
  • 1
    @Olaf Hence typically. This general rubbishing of `double` has to stop. The OP should consult this: https://math.stackexchange.com/questions/827565/how-to-find-the-nth-term-of-tribonacci-series – Bathsheba Jun 19 '17 at 12:33
  • @Bathsheba: That's not related to the question which is about printing. But yes, to print such long integers one needs to have such integers in the first place. – too honest for this site Jun 19 '17 at 12:34
  • @Olaf: Not necessarily. You don't need to keep the whole integer in memory in order to print it. But that approach might not be possible here. My number theory is rusty. – Bathsheba Jun 19 '17 at 12:35
  • @FelixPalmen Yeah it was thats why i want to know how to do it. Using double is the best i could do is what i meant because i couldnt think of what else to use. – Roark Jun 19 '17 at 12:36
  • 1
    @Roark: You cannot use a `double` to represent the individual terms in the sequence. – Bathsheba Jun 19 '17 at 12:37
  • 1
    @Roark definitely not. A common "simple" approach is to use `char` arrays and calculate based on the digits represented in ASCII. While this has terrible performance, it's an approach you could manage to implement correctly during an exam. –  Jun 19 '17 at 12:38
  • @Bathsheba: You need the previous values for the addition, so you have to have them in memory anyway. But that's not what the question asks for and even without the calculation, the question is too broad already. In fact, it is completely irrelevant for the question **how** these values are generated. – too honest for this site Jun 19 '17 at 12:39
  • @Roark "good Code" would be to represent the big numbers by combining multiple smaller unsigned integer types, e.g. in an array. But this is a lot of work to get right, and printing involves some conversion to BCD (lookup "double dabble"), so it's not suitable for implementing during an exam. –  Jun 19 '17 at 12:39
  • @Roark: EIther you have a bad teacher or you did not pay heed to the course. Either way, you might want to talk to your tutor. – too honest for this site Jun 19 '17 at 12:39
  • I *think* there exists a way of extracting the nth digit from the mth term, in which case there's little need for large number libraries. But if this was for a CompSci exam I am indeed missing the point: the student would have been better off basing a solution from `unsigned char` arrays. – Bathsheba Jun 19 '17 at 12:40
  • @FelixPalmen Yeah thats a good idea for under exam conditions, but how would you even separate the digits of the number without first storing it somewhere? – Roark Jun 19 '17 at 12:40
  • @Roark a suitably sized `char` array *does* store them? What do you mean? –  Jun 19 '17 at 12:41
  • @Olaf The question was given under exam conditions, so i wouldn't have time to create a solution that takes a lot of time to create. Throughout the course we were not given any practice questions or even told about how to solve a problem like this because we never even used numbers this large. – Roark Jun 19 '17 at 12:43
  • @FelixPalmen But to store the number in the array would you not first have to have the number stored in a data type and then copy over each individual digit? – Roark Jun 19 '17 at 12:45
  • @Roark I don't know how you obtain your input number? If it's from a file or `stdin` (keyboard), it *is* already a sequence of ascii-represented digits. –  Jun 19 '17 at 12:46
  • @FelixPalmen I'll edit the post to show how i obtained the input number as i wrote it in the exam. – Roark Jun 19 '17 at 12:47
  • "Throughout the course we were not given any practice questions or even told about how to solve a problem like this" - And you think real-live proggramming problems are different? Welcome to RL! Programming **is** about solving problems you have not encountered before. Anything else would just be boring. – too honest for this site Jun 19 '17 at 12:49
  • @Olaf Yes that's true, that's why I wanted to get some help on how to solve this one, because I just couldn't think of a solution. – Roark Jun 19 '17 at 12:54
  • 1
    Per [Tribonacci Number](http://mathworld.wolfram.com/TribonacciNumber.html), the function is incorrect. I'd expect something with `return tribonacci(n-3) + tribonacci(n-2) + tribonacci(n-1);` rather than `return fibonacci(n-3) ...` and something else than `if(n < 4){ return 1;` like `if(n < 4){ return 1 + n==3;` – chux - Reinstate Monica Jun 19 '17 at 14:37
  • You're completely right i wrote the fibonacci bit wrong. – Roark Jun 21 '17 at 13:19

4 Answers4

2

You should use some arbitrary-precision arithmetic library (a.k.a. Bigints or bignums) if your teacher allows them. I recommend GMPlib, but there are others.

See also this answer (notably if your teacher wants you to write some crude arbitrary precision addition).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
2

You require a different algorithm. The code posted cannot suffer from an integer overflow, as it does all its calculations in doubles. So you are probably getting a stack overflow instead. The posted code uses exponential time and space, and at N=38 that exponential space is probably overflowing the stack. Some alternatives, in increasing order of efficiency and complexity:

  1. Use the "memoization" technique to optimize the algorithm you have.
  2. Build up the answer starting by calculating N=4, and iterating upwards. No recursion is then needed.
  3. Do the mathematics (or find someone who can) to get the "closed form solution" that allows direct calculation of the answer. See https://en.wikipedia.org/wiki/Fibonacci_number#Closed-form_expression for how this works for regular fibonacci numbers.

You will also need a "big number" data structure - see other answers.

Martin Randall
  • 308
  • 2
  • 9
2

For a development time limited exam solution, I'd definitely go for the quick & dirty approach, but I wouldn't exactly complete it within 15 minutes.

The problem size is restricted to 1500 characters, computing tribonacci indicates that you will always need to carry subresult N-3, N-2 and N-1 in order to compute subresult N. So lets define a suitable static data structure with the right starting values (its 1;1;1 in your question, but I think it should be 0;1;1):

char characterLines[4][1501] = { { '0', 0 }, { '1', 0 }, { '1', 0 } };

Then define an add function that operates on character arrays, expecting '\0' as end of array and the character numbers '0' to '9' as digits in a way that the least significant digit comes first.

void addBigIntegerCharacters(const char* i1, const char* i2, char* outArray)
{
    int carry = 0;
    while(*i1 && *i2)
    {
        int partResult = carry + (*i1 - '0') + (*i2 - '0');
        carry = partResult / 10;
        *outArray = (partResult % 10) + '0';
        ++i1; ++i2; ++outArray;
    }
    while(*i1)
    {
        int partResult = carry + (*i1 - '0');
        carry = partResult / 10;
        *outArray = (partResult % 10) + '0';
        ++i1; ++outArray;
    }
    while(*i2)
    {
        int partResult = carry + (*i2 - '0');
        carry = partResult / 10;
        *outArray = (partResult % 10) + '0';
        ++i2; ++outArray;
    }
    if (carry > 0)
    {
        *outArray = carry + '0';
        ++outArray;
    }
    *outArray = 0;
}

Compute the tribonacci with the necessary number of additions:

// n as 1-based tribonacci index.
char* computeTribonacci(int n)
{
    // initialize at index - 1 since it will be updated before first computation
    int srcIndex1 = -1;
    int srcIndex2 = 0;
    int srcIndex3 = 1;
    int targetIndex = 2;

    if (n < 4)
    {
        return characterLines[n - 1];
    }
    n -= 3;
    while (n > 0)
    {
        // update source and target indices
        srcIndex1 = (srcIndex1 + 1) % 4;
        srcIndex2 = (srcIndex2 + 1) % 4;
        srcIndex3 = (srcIndex3 + 1) % 4;
        targetIndex = (targetIndex + 1) % 4;
        addBigIntegerCharacters(characterLines[srcIndex1], characterLines[srcIndex2], characterLines[targetIndex]);
        addBigIntegerCharacters(characterLines[targetIndex], characterLines[srcIndex3], characterLines[targetIndex]);
        --n;
    }

    return characterLines[targetIndex];
}

And remember that your least significant digit comes first when printing the result

void printReverse(const char* start)
{
    const char* printIterator = start;
    while (*printIterator)
    {
        ++printIterator;
    }
    do
    {
        putchar(*(--printIterator));
    } while (printIterator != start);
}


int main()
{
    char* c = computeTribonacci(50); // the real result is the array right-to-left

    printReverse(c);
}

As said, this is kindof quick & dirty coded, but still not within 15 minutes.

The reason why I use a separate char per decimal digit is mainly readability and conformity to the way how decimal math works on pen&paper, which is an important factor when development time is limited. With focus on runtime constraints rather than development time, I'd probably group the numbers in an array of unsigned long long, each representing 18 decimal digits. I would still focus on decimal digit groupings, because this is a lot easier to print as characters using the standard library functions. 18 because I need one digit for math overflow and 19 is the limit of fully available decimal digits for unsigned long long. This would result in a few more changes... 0 couldn't be used as termination character anymore, so it would probably be worth saving the valid length of each array. The principle of add and computeTribonacci would stay the same with some minor technical changes, printing would need some tweaks to ensure a length 18 output for each group of numbers other than the most significant one.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • Read [ask] and remember we are not a coding service for lazy homeworkers/students. – too honest for this site Jun 19 '17 at 13:46
  • 1
    @Olaf this is not a homework question, but a past exam question. While SO is not a coding service there are some problems that capture my interest and in *How to Ask* there is nothing that keeps me from answering such questions more detailed than strictly necessary. – grek40 Jun 19 '17 at 13:51
  • @alinsoar I think the other answers provide plenty of math related resources. Also the specific problem of tribonacci and printing large numbers is not that interesting from a math point of view, since it doesn't require any math operator other than `Add`. Maybe I'll put some more background information why I implemented some parts the way I did and what alternatives come to mind, but I won't go the math theory path for this answer. – grek40 Jun 19 '17 at 14:04
  • @grek40: Nothing personal, but people tell a lot to get others doing their job. If that is no homework, you'd had enough time to solve this on your own. It might be a bit challenging for an exam, but without time-pressure, a student should be able to solve this himself; at least show **some** effort. You don't! – too honest for this site Jun 19 '17 at 14:05
  • @Olaf maybe I'm mis-reading you, but I feel like you blame me for the question (which is not mine) rather than for the answer... – grek40 Jun 19 '17 at 14:07
  • 1
    @grek40: You are right, sorry about that. Just replace "you" etc with "OP". You don't really help anyone by giving full code. OP the least. – too honest for this site Jun 19 '17 at 14:09
1

You need to replace the + operation with an operator ADD made by yourself and encode BigIntegers as you wish -- there are lots of ways to encode BigIntegers.

So you need to define yourself a datatype BigInteger and the following operations

ADD : BigInteger, BigInteger -> BigInteger 
1+  : BigInteger -> BigInteger
2-  : BigInteger -> BigInteger
<4  : BigInteger -> boolean
The constants 1,2,4 as BigInteger

and after having replaced these things write a standard function to compute fibb in linear time and space.

alinsoar
  • 15,386
  • 4
  • 57
  • 74