A possible implementation is to use a custom structure to store the numbers instead of a built-in type. You could for instance store numbers like this:
template <int... Digits>
struct number<Digits... > { };
Note: For the sake of simplicity when adding, I store the digits in reverse order, so the number 275
is stored as number<5, 7, 2>
.
Fibonacci only requires addition, so you simply have to define addition, e.g., a template add
(see the end of the answer for the actual implementation).
You can then define the fib
template quite easily:
template <int N>
struct fib_impl {
using type = add_t<
typename fib_impl<N-1>::type,
typename fib_impl<N-2>::type,
typename fib_impl<N-3>::type>;
};
template <>
struct fib_impl<0> { using type = number<0>; };
template <>
struct fib_impl<1> { using type = number<0>; };
template <>
struct fib_impl<2> { using type = number<1>; };
template <int N>
using fib = typename fib_impl<N>::type;
And with an appropriate output operator (see below), you can print the 100th Tribonacci number:
int main() {
std::cout << fib<100>{} << "\n";
}
Which outputs:
53324762928098149064722658
While the 100th is not present in the OEIS, you can check that the 37th one is correct:
static_assert(std::is_same_v<fib<37>, number<2, 5, 8, 6, 3, 4, 2, 3, 1, 1>>);
Implementation of operator<<
:
std::ostream& operator<<(std::ostream &out, number<>) {
return out;
}
template <int Digit, int... Digits>
std::ostream& operator<<(std::ostream &out, number<Digit, Digits... >) {
// Do not forget that number<> is in reverse order:
return out << number<Digits... >{} << Digit;
}
Implementation of the add
template:
- This is a small
cat
utility to concatenate numbers:
// Small concatenation utility:
template <class N1, class N2>
struct cat;
template <int... N1, int... N2>
struct cat<number<N1... >, number<N2... >> {
using type = number<N1... , N2...>;
};
template <class N1, class N2>
using cat_t = typename cat<N1, N2>::type;
- The actual implementation of the addition:
template <class AccNumber, int Carry, class Number1, class Number2>
struct add_impl;
template <class AccNumber, int Carry>
struct add_impl<AccNumber, Carry, number<>, number<>> {
using type = std::conditional_t<Carry == 0, AccNumber, cat_t<AccNumber, number<1>>>;
};
template <class AccNumber, int Carry,
int Digit2, int... Digits2>
struct add_impl<AccNumber, Carry, number<>, number<Digit2, Digits2...>> {
using type = typename add_impl<
cat_t<AccNumber, number<(Digit2 + Carry) % 10>>,
(Digit2 + Carry) / 10,
number<Digits2... >, number<>>::type;
};
template <class AccNumber, int Carry,
int Digit1, int... Digits1>
struct add_impl<AccNumber, Carry, number<Digit1, Digits1... >, number<>> {
using type = typename add_impl<
cat_t<AccNumber, number<(Digit1 + Carry) % 10>>,
(Digit1 + Carry) / 10,
number<Digits1... >, number<>>::type;
};
template <class AccNumber, int Carry,
int Digit1, int... Digits1, int Digit2, int... Digits2>
struct add_impl<AccNumber, Carry, number<Digit1, Digits1... >, number<Digit2, Digits2...>> {
using type = typename add_impl<
cat_t<AccNumber, number<(Digit1 + Digit2 + Carry) % 10>>,
(Digit1 + Digit2 + Carry) / 10,
number<Digits1... >, number<Digits2... >>::type;
};
- A short wrapper:
template <class... Numbers>
struct add;
template <class Number>
struct add<Number> {
using type = Number;
};
template <class Number, class... Numbers>
struct add<Number, Numbers... > {
using type = typename add_impl<
number<>, 0, Number, typename add<Numbers... >::type>::type;
};
template <class... Numbers>
using add_t = typename add<Numbers... >::type;
template <int... Digits> struct Number;
. Fibonacci sequence only require addition, so you only have to implement an addition. You could even represent the number in base-2 for simpler addition. – Berchtesgadenlong long
, the limit is reached atfib<72>
(signed long long) orfib<73>
(unsigned long long) – Canary