在C ++编译期间检查数字是否为素数

时间:2013-05-26 16:06:09

标签: c++ compilation c-preprocessor primes

我有一个模板类,它将无符号整数作为模板参数,但我必须确保该数字是素数。例如,我可以在构造函数中检查它,但在编译期间最好这样做。

这是我正在使用的Assert模板:

template <bool statement>
class Assert;

template <>
struct Assert<true> {};

我可以在任何要编译的代码中使用我的条件作为参数创建这种类型的对象,如果该条件为false则不会编译。问题是我必须检查一些数字是否为素数。让它成为n。

我想出了包含一个单独的文件“PrimeTest.h”的想法,并尝试通过在该文件中包含相同的文件将n从n-1除以n。这就是我使用它的方式:

#define SUSPECT n
#include "PrimeTest.h"

这是“PrimeTest.h”:

#ifdef SUSPECT

    #ifndef CURRENT
        #define CURRENT (SUSPECT-1)
    #endif // CURRENT

    #ifndef FINISHED

    #if CURRENT>100
        #define IS_PRIME
        #define FINISHED
    #else
        #if SUSPECT%CURRENT==0
            #define IS_NOT_PRIME
            #define FINISHED
        #else
            #define CURRENT (CURRENT-1)  // THAT DOES NOT WORK!!!
            #include "PrimeTest.h"
        #endif // SUSPECT % CURRENT != 0
    #endif

    #endif // FINISHED

#endif // SUSPECT

但是问题在于:我无法以任何方式减少CURRENT,包括临时值和#pragma push_macro指令。 任何想法如何做到这一点?

4 个答案:

答案 0 :(得分:6)

您无需预处理器即可在编译时计算内容。 通常,在需要计算时,您可以使用模板元编程(或者在他的答案中由chris建议的constexpr函数)

通过模板元编程,您可以按如下方式解决任务:

首先定义一个模板,可以在编译时检查给定值N是否可以D除以,或者任何低于D的值是否大于1。

template <int N, int D>
struct tmp {
    static const bool result = (N%D) && tmp<N,D-1>::result;
};

template <int N>
struct tmp<N,1> {
    static const bool result = true;
};

仅当数字2,3,... tmp<N,D>::result未划分true时,值D才为N

使用上面的工具,创建is_prime编译时检查程序非常简单:

template <int N>
struct is_prime {
    static const bool result = tmp<N,N-1>::result;
};

is_prime<N>::result为素数时,编译时值trueN,否则为false。该值可以提供给其他模板,例如您的Assert

答案 1 :(得分:6)

C ++ 11 constexpr版本,应该能够在任何实现建议的递归深度限制的编译器上检查大约1500的数字:

constexpr bool is_prime_helper( std::size_t target, std::size_t check ) {
  return (check*check > target) ||
    (
      (target%(check+1) != 0)
      && (target%(check+5) != 0)
      && is_prime_helper( target, check+6 )
    );
}
constexpr bool is_prime( std::size_t target ) {
  return (target != 0) && (target !=1) &&
    ( ( target == 2 || target == 3 || target == 5 )
    || ((target%2 != 0) && (target%3 != 0) && (target%5)!=0 &&
    is_prime_helper( target, 6 )));
}

为了改善这一点,我们使用二叉搜索树做了一些乐趣:

#include <cstddef>

constexpr bool any_factors( std::size_t target, std::size_t start, std::size_t step) {
  return
      !(start*start*36 > target)
  &&
  (
    ( (step==1)
      && (
        (target%(start*6+1) == 0)
        || (target%(start*6+5) == 0)
      )
    )
    ||
    ( (step > 1)
      &&
      (
        any_factors( target, start, step/2 )
        || any_factors( target, start+step/2, step-step/2 )
      )
    )
  );
}

然后我们这样使用:

constexpr bool is_prime( std::size_t target ) {
  // handle 2, 3 and 5 explicitly:
  return 
    (target == 2 || target == 3 || target == 5)
  ||
    (
      target != 0
      && target != 1
      && target%2 != 0
      && target%3 != 0
      && target%5 != 0
      && !any_factors( target, 1, target/6 + 1 ) // can make that upper bound a bit tighter, but I don't care
    );
}
#include <iostream>
int main() {
  std::cout << "97:" << is_prime(97) << "\n";
  std::cout << "91:" << is_prime(91) << "\n";
}

将递归〜log_2(target/6)次,这意味着C ++ 11标准要求编译器实现的constexpr表达式的递归限制不再是一个问题。

Live example,嵌入了调试功能。

这基本上可以达到系统上std::size_t的限制。我用111111113测试了它。

这在中非常容易,因为我们不再需要单行constexpr函数,而是需要理智的结构。 See here

constexpr bool any_factors( std::size_t target, std::size_t start, std::size_t step ) {
  if (start*start*36 > target)
  {
      return false;
  }
  if (step==1)
  {
    bool one_mod_6 = target%(start*6+1) == 0;
    bool five_mod_6 = target%(start*6+5) == 0;
    return one_mod_6 || five_mod_6;
  }

  bool first_half = any_factors(target, start, step/2);
  bool second_half = any_factors(target, start+ step/2, (step+1)/2);

  return first_half || second_half;  
}

答案 2 :(得分:3)

这是一个业余解决方案,用于正数并在编译时完成,但由于递归限制,它不会在它破坏之前走得太远。我想你可以添加一个手动计算的平方根参数,以使其达到现在的平方。但它确实利用了C ++ 11的constexpr函数,使语法更好,无需额外的工作。无论如何,这可能是一个良好的开端,我期待看到更好的答案。

constexpr bool IsPrime(std::size_t N, std::size_t I = 2) {
    return (N !=  2 ? N%I : true) //make 2 prime and check divisibility if not 2
        && (N >= 2) //0 and 1 are not prime
        && (I >= N-1 ? true : IsPrime(N, I+1)); //stop when I is too big
}

您甚至可以为您完成平方根。对于此示例,我将IsPrime变为帮助程序,以便只能使用IsPrime调用N

//basically does ceil(sqrt(N))
constexpr std::size_t Sqrt(std::size_t N, std::size_t I = 2) {
    return I*I >= N ? I : Sqrt(N, I+1);
}

//our old IsPrime function with no default arguments; works with sqrt now
constexpr bool IsPrimeHelper(std::size_t N, std::size_t sqrt, std::size_t I) {
    return (N != 2 ? N%I : true) 
        && (N >= 2) 
        && (I >= sqrt ? true : IsPrimeHelper(N, sqrt, I+1));
}

//our new prime function: this is the interface for the user
constexpr bool IsPrime(std::size_t N) {
    return IsPrimeHelper(N, Sqrt(N), 2);
}

对我来说,这个新版本的编号为521,而另一个版本失败了。它甚至适用于9973.新的预期高点应该是旧的平方。如果你想更进一步,你甚至可以修改IsPrimeHelperI为2时增加1,在I不是2时增加2.这将导致一个新的高点两次这个。

答案 3 :(得分:0)

对于可移植到传统C编译器的内容:

// preprocessor-compatible macro telling if x is a prime at most 15-bit
#define PRIME15(x) (((x)>1)&(((x)<6)*42+545925250)>>((x)%30&31)&&((x)<49\
||(x)%7&&(x)%11&&(x)%13&&(x)%17&&(x)%19&&(x)%23&&(x)%29&&(x)%31&&(x)%37\
&&(x)%41&&(x)%43&&(x)%47&&((x)<2809||(x)%53&&(x)%59&&(x)%61&&(x)%67\
&&(x)%71&&(x)%73&&(x)%79&&(x)%83&&(x)%89&&(x)%97&&(x)%101&&(x)%103\
&&(x)%107&&(x)%109&&(x)%113&&(x)%127&&(x)%131&&(x)%137&&(x)%139&&(x)%149\
&&(x)%151&&(x)%157&&(x)%163&&(x)%167&&(x)%173&&(x)%179&&(x)<32761)))
  • (x)>1,因为所有素数都至少为2
  • (x)<6是因为因为我们对素数2 3 5进行了特殊大小写
  • 42also这些特殊素数(1<<2)+(1<<3)+(1<<5)的位图
  • 545925250是1 7 11 13 17 19 19 23 29的位图,用于快速除以2 3 5
  • >>((x)%30&31)访问所述快速测试的位图¹
  • (x)<49(分别为(x)<2809(x)<32761),因为我们想知道7 =√49(分别为53 =√2809和181 =√32761)素
  • (x)%7&..&&(x)%47(x)%53&&..&&(x)%179,因为我们测试了可除性
  • 179,因为下一个质数的平方超过15位。
  • 如果宏用于生成代码,那么效率很高。

    Try it online!


    ¹&31是一种变通工具,可以解决否定x的警告。不可以,用&替换第一个&&1&不会将其切入嵌入式CPU的某个编译器,该编译器在最大警告级别下报告常量表达式中的错误,并带有负移,包括在子表达式中,短路布尔值要求不评估。

    相关问题
    最新问题