【问题标题】:Check if number is prime during compilation in C++在 C++ 编译期间检查数字是否为素数
【发布时间】:2013-05-21 14:18:46
【问题描述】:

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

这是我正在使用的断言模板:

template <bool statement>
class Assert;

template <>
struct Assert<true> {};

我可以简单地在任何将要编译的代码中创建一个这种类型的对象,使用我的条件作为参数,如果条件为假,它将不会编译。问题是我必须检查某个数字是否是素数。就这样吧。

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

#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 指令。 任何想法如何做到这一点?

【问题讨论】:

  • 你用的是什么编译器?您可以访问任何 C++11 功能吗?
  • 我使用的是 Microsoft Visual C++,它还不支持 constexpr。但这很好,我已经设法使用额外的模板结构来解决这个问题。
  • 是的,它们大致相当。如果您只需要小素数@CygnusX1 的答案就可以了。如果您需要更大的数字,我在下面所做的constexpr 答案可以适用于基于template 的解决方案。

标签: c++ compilation c-preprocessor primes


【解决方案1】:

您不需要预处理器在编译时计算某些东西。 通常,当需要计算时,您使用模板元编程(或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、...D 不除 N 时,tmp&lt;N,D&gt;::result 的值才为 true

使用上面的工具,创建is_prime compile-time checker 相当容易:

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

现在,当N 为素数时,编译时值is_prime&lt;N&gt;::resulttrue,否则为false。该值可以提供给其他模板,例如您的Assert

【讨论】:

  • 我回滚了建议的std::integral_constant,因为 (a) 它是 C++11 功能,而上述解决方案坚持使用旧 C++。使用 C++11,克里斯的constexpr 更干净。 (b) 因为那时is_prime&lt;N&gt;::result 不存在,但我仍然在下一段中引用它。
【解决方案2】:

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 标准要求编译器至少实现的 512 的 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 和 5 工作也很容易。它只涉及做target == 2 || target%2 != 0(在括号中)。而0和1可以用target &gt;= 2处理。
  • 1111111111111111111 花了一分钟,但它奏效了。我可能玩得太开心了。
  • @chris Ya,我最初发布的内容有一些错误。 “二叉树”版本应该在上面工作,除了 0 和 1.. 让我去修复那些。
  • @chris 下一步将支持任意精度整数。
  • @tntxtnt 抱歉,在我的分而治之中出现了一个错误。固定。
【解决方案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。新的预期高点应该是旧的平方。如果你想走得更远,你甚至可以修改IsPrimeHelper,当I 为2 时增加1,当I 不是2 时增加2。这将导致新高大约是这个的两倍。

【讨论】:

  • 我希望 IsPrime 函数采用单个参数。为什么要带两个?否则,使用constexpr 的好主意。我仍在思考“旧”C++ ......
  • @CygnusX1,这只是为了将您需要的实体数量减少到一个。您可以将它包装在只需要一个的东西中,并使用第二个参数 2 来调用它,而不是默认它。您仍然可以将其用作static_assert(IsPrime(11), "11 is not prime.");
  • @CygnusX1,我决定在这个过程中加入平方根的想法,成为你的帮手:)
  • 512 是constexpr 的建议最大递归深度。您可以通过在素数之间(在 2 之后)执行 2 的步长来使代码效率提高一倍,通过在 6 的步长中执行另外 50%(仅在前 6 个之后检查 X+1 和 X+5数字)。
  • @Yakk,在我实现平方根的想法后,我开始在底部提出建议:)
【解决方案4】:

对于可移植到传统 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)&gt;1 因为所有素数至少为 2
  • (x)&lt;6 因为我们特例化了素数 2 3 5
  • 42also 这些特殊素数的位图 (1&lt;&lt;2)+(1&lt;&lt;3)+(1&lt;&lt;5)
  • 545925250 是 1 7 11 13 17 19 23 29 的位图,用于快速测试可被 2 3 5 整除
  • &gt;&gt;((x)%30&amp;31) 访问所述位图以进行所述快速测试¹
  • (x)&lt;49(分别是 (x)&lt;2809(x)&lt;32761)因为我们想告诉 7=√49(分别是 53=√2809 和 181=√32761)素数
  • (x)%7&amp;..&amp;&amp;(x)%47(x)%53&amp;&amp;..&amp;&amp;(x)%179 因为我们测试可分性
  • 179 因为下一个素数的平方超过 15 位。

如果用宏来生成代码,效率还是蛮高的。

Try it online!


¹&amp;31 是一种解决负面x 警告的方法。不,用&amp;&amp;1&amp; 替换第一个&amp; 不会为嵌入式CPU 的某个编译器削减它,该编译器在最大警告级别下报告带有负移位的常量表达式中的错误,包括短路布尔值的子表达式不需要评估。

【讨论】:

    猜你喜欢
    • 2013-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-19
    • 2019-03-15
    相关资源
    最近更新 更多