Search code examples
c++boosttemplate-meta-programminggmpboost-multiprecision

Creating a static lookup table when the integer type is Boost multiprecision number/cpp_int


I'd like to create a static lookup table for powers of ten returned as type boost::mulitprecision's uint256_t:

static uint256_t power_ten(const uint8_t n)
{
    static const uint256_t table[] = 
    { 
        1,
        10
        // etc
    };

    return table[n];
}

However, I won't be able to define the literals for 256 bits because C++ literals are ints.

Consequently I thought I could use template metaprogramming to calculate the powers so I don't need to specify literals, similar to this Fibonacci example I found:

#include <boost/multiprecision/cpp_int.hpp>

using namespace boost::multiprecision;

using Integer = int;            // Works
//using Integer = uint256_t;    // Causes compiler errors

template<Integer n>
struct fibonacci
{
  static constexpr Integer value = fibonacci<n-1>::value + fibonacci<n-2>::value;
};

template<>
struct fibonacci<0>
{
  static constexpr Integer value = 0;
};

template<>
struct fibonacci<1>
{
  static constexpr Integer value = 1;
};

int main()
{   
    Integer a = fibonacci<40>::value;
    std::cout << a << std::endl;
}

This code works for int. However, if I comment/uncomment to use uint256_t I get these compiler errors:

clang-14: warning: -lquadmath: 'linker' input unused [-Wunused-command-line-argument]
<source>:8:18: error: a non-type template parameter cannot have type 'Integer' (aka 'number<cpp_int_backend<256, 256, unsigned_magnitude, unchecked, void>>')
template<Integer n>
                 ^
<source>:11:28: error: declaration of constexpr static data member 'value' requires an initializer
  static constexpr Integer value = fibonacci<n-1>::value + fibonacci<n-2>::value;
                           ^
2 errors generated.
ASM generation compiler returned: 1
<source>:8:18: error: a non-type template parameter cannot have type 'Integer' (aka 'number<cpp_int_backend<256, 256, unsigned_magnitude, unchecked, void>>')
template<Integer n>
                 ^
<source>:11:28: error: declaration of constexpr static data member 'value' requires an initializer
  static constexpr Integer value = fibonacci<n-1>::value + fibonacci<n-2>::value;

https://godbolt.org/z/qsK8rG5WG

Is there another way to achieve this?

UPDATE

I've just tried populating the static table using uint256_t with a string constructor, as per the comments. This seemed to work but once I reach:

uint256_t("1000000000000000000000000000000000000000000000000000000000000000000000000000000")

std::cout outputs the wrong value:

73663286101470436611432119930496737173840122674875487684339327936694962880512

Solution

  • Instead of using costly string initialization, consider an expression that is likely to be more efficient:

    Live On Coliru

    #include <boost/multiprecision/cpp_int.hpp>
    
    using boost::multiprecision::uint512_t;
    
    static uint512_t power_ten(const uint8_t n)
    {
        static const uint512_t table[] = {
            pow(uint512_t(10), 0),   pow(uint512_t(10), 1),  pow(uint512_t(10), 2),  pow(uint512_t(10), 3),
            pow(uint512_t(10), 4),   pow(uint512_t(10), 5),  pow(uint512_t(10), 6),  pow(uint512_t(10), 7),
            pow(uint512_t(10), 8),   pow(uint512_t(10), 9),  pow(uint512_t(10), 10), pow(uint512_t(10), 11),
            pow(uint512_t(10), 12),  pow(uint512_t(10), 13), pow(uint512_t(10), 14), pow(uint512_t(10), 15),
            pow(uint512_t(10), 16),  pow(uint512_t(10), 17), pow(uint512_t(10), 18), pow(uint512_t(10), 19),
            pow(uint512_t(10), 20),  pow(uint512_t(10), 21), pow(uint512_t(10), 22), pow(uint512_t(10), 23),
            pow(uint512_t(10), 24),  pow(uint512_t(10), 25), pow(uint512_t(10), 26), pow(uint512_t(10), 27),
            pow(uint512_t(10), 28),  pow(uint512_t(10), 29), pow(uint512_t(10), 30), pow(uint512_t(10), 31),
            pow(uint512_t(10), 32),  pow(uint512_t(10), 33), pow(uint512_t(10), 34), pow(uint512_t(10), 35),
            pow(uint512_t(10), 36),  pow(uint512_t(10), 37), pow(uint512_t(10), 38), pow(uint512_t(10), 39),
            pow(uint512_t(10), 40),  pow(uint512_t(10), 41), pow(uint512_t(10), 42), pow(uint512_t(10), 43),
            pow(uint512_t(10), 44),  pow(uint512_t(10), 45), pow(uint512_t(10), 46), pow(uint512_t(10), 47),
            pow(uint512_t(10), 48),  pow(uint512_t(10), 49), pow(uint512_t(10), 50), pow(uint512_t(10), 51),
            pow(uint512_t(10), 52),  pow(uint512_t(10), 53), pow(uint512_t(10), 54), pow(uint512_t(10), 55),
            pow(uint512_t(10), 56),  pow(uint512_t(10), 57), pow(uint512_t(10), 58), pow(uint512_t(10), 59),
            pow(uint512_t(10), 60),  pow(uint512_t(10), 61), pow(uint512_t(10), 62), pow(uint512_t(10), 63),
            pow(uint512_t(10), 64),  pow(uint512_t(10), 65), pow(uint512_t(10), 66), pow(uint512_t(10), 67),
            pow(uint512_t(10), 68),  pow(uint512_t(10), 69), pow(uint512_t(10), 70), pow(uint512_t(10), 71),
            pow(uint512_t(10), 72),  pow(uint512_t(10), 73), pow(uint512_t(10), 74), pow(uint512_t(10), 75),
            pow(uint512_t(10), 76),  pow(uint512_t(10), 77), pow(uint512_t(10), 78), pow(uint512_t(10), 79),
            pow(uint512_t(10), 80),  pow(uint512_t(10), 81), pow(uint512_t(10), 82), pow(uint512_t(10), 83),
            pow(uint512_t(10), 84),  pow(uint512_t(10), 85), pow(uint512_t(10), 86), pow(uint512_t(10), 87),
            pow(uint512_t(10), 88),  pow(uint512_t(10), 89), pow(uint512_t(10), 90), pow(uint512_t(10), 91),
            pow(uint512_t(10), 92),  pow(uint512_t(10), 93), pow(uint512_t(10), 94), pow(uint512_t(10), 95),
            pow(uint512_t(10), 96),  pow(uint512_t(10), 97), pow(uint512_t(10), 98), pow(uint512_t(10), 99),
            pow(uint512_t(10), 100),
            // etc
        };
    
        return table[n];
    }
    
    int main() {
        for (auto n = 0; n<100; ++n) 
            std::cout << power_ten(n) << "\n";
    }
    

    Prints

    1
    10
    100
    1000
    10000
    100000
    ...
    100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    

    Bonus

    Even cleaner, make it procedural:

    Live On Coliru

    static uint512_t power_ten(const uint8_t n) {
        static auto const table = [] {
            std::array<uint512_t, 155> tmp;
            for(int exp = 0; auto& el : tmp)
                el = pow(uint512_t(10), exp++);
            return tmp;
        }();
    
        return table.at(n); // or [n]
    }
    

    Printing the same and more.