0

I am currently implementing a code that runs Fibonacci sequence (up to the 60th number) in riscv32I, which means that the memory address that I can use only has 32bits.

I have first implemented the code in C, and then in Assembly, but I am curious if the algorithm I used has a name so I can do more research. The code is as such,

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

int main() {
    uint32_t n1 = 0;        // first pre number  (n - 2)
    uint32_t n2 = 1;        // second pre number (n - 1)
    uint32_t add = 0;       // current number

    uint32_t store_hi = 0;  
    uint32_t store_lo = 0; 
    uint32_t result;        // result
    uint32_t carry;         // carry bit

    for (int i = 2; i < 61; i++) {
        carry = 0;                              // reset carry bit
        add = (uint32_t)(n2 + n1);              // calculate current fib number
        
        if (add < n1) {                         // if overflow  
            carry = 1;                          // set carry bit
        }

        result = store_hi + store_lo;           // keeping track of higher bits
        result = result + carry;                // add carry bit

        store_lo = store_hi;                    // 
        n1 = n2;                                // update first pre number
        store_hi = result;                      // 
        n2 = add;                               // update second pre number
    }

    printf("Result32: 0x%08" PRIx32 " 0x%08" PRIx32 "\n", result, add);
    uint64_t result64 = ((uint64_t)result << 32) | add;
    printf("Result64: 0x%016" PRIx64 " -> %" PRId64 "\n", result64, result64);
}

Running the code gives

Result32: 0x00000168 0x6c8312d0
Result64: 0x000001686c8312d0 -> 1548008755920

The basic concept is that because the Fibonacci number gets too big to fit within a single 32bit memory address, we have to split it into 32bit memory address, one holding the upper bit, and one holding the lower bit.

Let's generalize the above algorithm to a 4 bit memory space, to make it easier to follow the algorithm. This means that the maximum int can be 16. Let ss set n1 = 10, n2 = 10.

Loop 1:
     add = 4 # (10 + 10 = 20, but overflow, so 20 % 16 = 4)
     carry = 1
     result = 1
     store_lo = 0
     store_hi = 1
     n1 = 10
     n2 = 4
     # output: 0x14, 0x1 hi bit, 0x4 lo bit, which is 10 + 10 = 20
Loop 2:
     add = 14
     carry = 0
     result = 1
     store_lo = 1
     store_hi = 1
     n1 = 4
     n2 = 14
     # output: 0x1e, 0x1 hi bit, 0xe or 14, lo bit, which is 10 + 20 = 30
loop 3:
     add = 2 (14 + 4 = 18, but overflow, so 18 % 16, 2)
     carry = 1
     result = 3
     store_lo = 1
     store_hi = 2
     n1 = 14
     n2 = 2
     #output: 0x32, 0x3 hi bit, 0x2 low bit, which is  20 + 30 = 50
.... and so on.

This should work for any base, but I am curious what this algorithm is denoted as, or if it is simply related to modules and powers?

Thanks!

akastack
  • 75
  • 7
  • any terminology that I have used wrongly, please help correct! Thanks! – akastack Sep 14 '22 at 05:07
  • 3
    C++ and C are different languages, choose one. Adding unrelated tags will not give you anything but downvotes. – Slava Sep 14 '22 at 05:12
  • 1
    (1) It isn't clear what memory addresses have to do with Fibonacci numbers. You are using integers, not addresses. (2) You are using `uint64_t` in one place. If you can use `uint64_t`, why not just use it everywhere? – n. m. could be an AI Sep 14 '22 at 05:24
  • @n.1.8e9-where's-my-sharem. my goal is to implement it in riscv32, so I want to keep the size in 32 bits, the resulting nth Fibonacci number should be stored as a result in memory, but one 32 bit memory location isn't enough. – akastack Sep 14 '22 at 05:27
  • 1
    It isn't clear how using uint64_t in some places is consistent with your goal. – n. m. could be an AI Sep 14 '22 at 05:46
  • in assembly you simply do 64bit addition on 32bit registers like this: `add l0,l1; adc h0,h1;` see [Can't make value propagate through carry](https://stackoverflow.com/a/26603589/2521214) – Spektre Sep 14 '22 at 05:58
  • @n.1.8e9-where's-my-sharem. it is just used to show put the output, how it gives the correct fibonacci number when combining the hi 32 bit and lo 32 bit – akastack Sep 14 '22 at 06:05
  • 1
    akastack, "one holding the upper bit, and one holding the lower bit." better as "one holding the most significant 32-bits, and one holding the least significant 32-bits" – chux - Reinstate Monica Sep 14 '22 at 14:08

2 Answers2

5

It's called Arbitrary-precision arithmetic, you can read more about it here.

Arbitrary-precision arithmetic, also called bignum arithmetic, multiple-precision arithmetic, or sometimes infinite-precision arithmetic, indicates that calculations are performed on numbers whose digits of precision are limited only by the available memory of the host system.

Semyon
  • 111
  • 4
3

One of the troubles of venturing from 32-bit to 64-bit is that the now distant horizon rapidly becomes as constraining as the former, "closer" horizon was.

Below is a sketch of an "ASCII digit" Fibonacci calculator. Its two seeds are "0" and "1" at the right end of two 20 character buffers. (20 is arbitrary, but takes one beyond the 60th value in the Fibonacci sequence.) This could be optimised and improved in a hundred different ways. It is a "powers of ten" version that uses two ASCII strings for its storage. One could, if one was patient, use 1000 character buffer, or 10,000 to go deep into the Fibonacci realm...

I hope you find this interesting.

EDIT: @Chux has pointed out that the sequence generated below indexes from 1, whereas indexing from 0 is correct. The simple fix (not shown here) would be to change three instances of ++fn to fn++ (an exercise left to the reader.) Thank you again, @Chux!

#include <stdio.h>

int main() {
    char f[2][20 + 1], *fmt = "%s   %-3d";
    int fn = 0;

    sprintf( f[0], "%20s", "0" );
    sprintf( f[1], "%20s", "1" );
    printf( fmt, f[ 0 ], ++fn );
    putchar( '\n' );
    printf( fmt, f[ 1 ], ++fn );

    for( bool evod = false; getchar() != EOF;  evod = !evod ) {

        for( int carry = 0, i = 20; --i >= 0; ) {
            if( f[ evod][i] == ' ' && f[!evod][i] == ' ' && carry == 0 ) break;
            int f1 = f[ evod][i] == ' ' ? 0 : f[ evod][i] - '0';
            int f2 = f[!evod][i] == ' ' ? 0 : f[!evod][i] - '0';

            f1 += f2 + carry; carry = f1 / 10; f[ evod][i] = f1%10 + '0';
        }
        printf( fmt, f[ evod], ++fn );
    }

    return 0;
}

Output

                   0   1
                   1   2
                   1   3
                   2   4
                   3   5
                   5   6
                   8   7
                  13   8
                  21   9
   /* omitted */
        591286729879   59
        956722026041   60
       1548008755920   61
       2504730781961   62
       4052739537881   63
Fe2O3
  • 6,077
  • 2
  • 4
  • 20
  • 1
    Passing in a variable as a format defeats many compilers format checking. Consider `#define fmt "%s %-3d"` instead. – chux - Reinstate Monica Sep 14 '22 at 13:59
  • 1
    Output indexes Fibonacci numbers in an off-by-1 manner. Better as [F(0) = 0 and F(1) = 1](https://oeis.org/A000045). – chux - Reinstate Monica Sep 14 '22 at 14:06
  • This *is* interesting! I might unroll the loop 2x to avoid the single array variable of 2 strings in favor of two string variables instead. That would simplify the indexing. I might also use array of raw bytes and add in '0' to each char during printing instead of during the math accumulation part. – Erik Eidt Sep 14 '22 at 14:44
  • @chux-ReinstateMonica Re: `char *fmt =`: My compiler ain't that modern. Users are free to customise this to suit. `:-)` Re: "indexing from zero". Thank you for link. About to EDIT a note into the description. `:-)` – Fe2O3 Sep 14 '22 at 20:14
  • @ErikEidt Go for your life! Just a fun little diversion... `:-)` – Fe2O3 Sep 14 '22 at 20:26