3

I was experimenting with meta programming to this point:

// compiled on Ubuntu 13.04 with:
// clang++ -O3 -ftemplate-depth-8192 -fconstexpr-depth=4096 -std=c++11 -stdlib=libc++ -lcxxrt -ldl compile-time-primes.cpp -o compile-time-primes

// assembly output with:
// clang++ -S -mllvm --x86-asm-syntax=intel -O3 -ftemplate-depth-8192 -fconstexpr-depth=4096 -std=c++11 -stdlib=libc++ -lcxxrt -ldl compile-time-primes.cpp -o compile-time-primes.asm

#include <array>
#include <iostream>

template<typename T>
constexpr bool is_prime(T number, T limit, T counter)
{
    return counter >= limit
        ? number % limit != 0
        : number % counter
            ? is_prime(number, number / counter, counter + 2)
            : false;
}

template<typename T>
constexpr bool is_prime(T n)
{
    return n == 2 || n == 3 || n == 5
        ? true
        : n <= 1 || n % 2 == 0
            ? false
            : is_prime(n, n / 3, T{3});
}

template<size_t n>
struct print_below;

template<> struct print_below<2> { inline static void primes() { std::cout << 2; } };
template<size_t n>
struct print_below
{
    inline static void primes()
    {
        print_below<n - 1>::primes();
        constexpr bool is_n_prime = is_prime(n);
        if(is_n_prime)
            std::cout << ", " << n;
    }
};

template <typename T, T... N>
struct primes { static const std::array<bool, sizeof...(N)> cache; };

template <typename T, typename L, T R>
struct append_param;

template <typename T, T... L, T R>
struct append_param<T, primes<T, L...>, R> { using primes = primes<T, L..., R>; };

template <size_t N>
struct indexer : append_param<size_t, typename indexer<N - 1>::primes, N - 1> {};

template <>
struct indexer<0> { using primes = primes<size_t>; };

template <typename T, T... N>
const std::array<bool, sizeof...(N)> primes<T, N...>::cache = {{ is_prime(N)... }};

int main()
{
    std::cout << "Some primes: \n";
    print_below<8000>::primes();
    std::cout << std::endl;

    const auto &primes_cache = indexer<1000>::primes::cache;

    for(size_t i = 1; i < primes_cache.size(); ++i)
        std::cout << i << (primes_cache[i] ? " is " : " is not ") << "prime" << std::endl;
}

Now I'm wondering whether there's a better tail recursive algorithm for is_prime that can be put in a constexpr function.

Is there anything much better than this?

  • Requirement: It must be tail recursive.
  • Desirable: To fit in a constexpr function.
oblitum
  • 11,380
  • 6
  • 54
  • 120

1 Answers1

2

Yes.

First of all, one of your main limits will be your recursion depth limit. Yours recurses once for every odd number from 3 to sqrt(N). With a ~1000 recursion limit, it means you'll only be able to handle numbers up to 1 million. You need to reduce the amount of recursion you are doing.

A way to do this is to do a divide-and-conquer search for the factors of your number N. With a bit of work, you can extend this to a limit on the order of 2^1000 (ie, other things, besides recursion limit, will make it fail to work first).

Second, instead of checking every odd number, check 6 mod 1 and 5, with a special case to check 2/3/5 at the start. Longer ranged patterns can be used as well than just a radius of 6.

Third, there are probabilistic primality tests that are sufficiently reliable that using them is the right answer. You could probably build a hard coded table of the numbers for which the test fails, check against that table, and do the tests otherwise, and make your upper limit much, much higher than what you can practically do otherwise.

Another issue with your design is that it tests for primes one at a time: ideally, you should build up a table of primes and use those to help with your prime testing. Some compilers will do memoization of previous results, you can look into exploiting that.

Peter G.
  • 14,786
  • 7
  • 57
  • 75
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • For tail recursive use cases, which I'm interested, does recursion limit apply? since object code doesn't contain recursion, only the source. Although I've provided a meta-programming sample, compile-time is not my only interest, hence the emphasis on tail recursion. – oblitum Jun 03 '13 at 04:01
  • @chico good point. I am not aware of an exception in the standard for tail recursion, although perhaps there should be. – Yakk - Adam Nevraumont Jun 03 '13 at 10:20
  • @chico As I missed the importance of tail-recursion in your question, do you want me to delete this answer to leave your question as yet unanswered? – Yakk - Adam Nevraumont Jun 03 '13 at 11:55
  • but, I'd be glad if "possible duplicate" and "vote for close" were removed, since the referred target of duplication doesn't mention anywhere tail recursion tips and constraints. – oblitum Jun 03 '13 at 16:25
  • @chico I cannot "unvote to close", but I can "vote to reopen" if it breaks 5. – Yakk - Adam Nevraumont Jun 03 '13 at 18:55