8

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

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

template <bool statement>
class Assert;

template <>
struct Assert<true> {};

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

我想出了包含一个单独的文件“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 指令。任何想法如何做到这一点?

4

4 回答 4

6

您不需要预处理器在编译时计算某些东西。通常,当需要计算时,您使用模板元编程(或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;
};

该值tmp<N,D>::resulttrue在数字 2、3、...D不除时N

使用上面的工具,创建is_prime编译时检查器相当容易:

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

现在编译时值is_prime<N>::resulttrue什么时候N是素数,false否则。该值可以提供给其他模板,例如Assert您的模板。

于 2013-05-26T16:31:15.257 回答
6

C++11constexpr版本应该能够在任何实现建议的递归深度限制的编译器上检查大约 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)次,这意味着constexprC ++ 11标准要求编译器至少实现的512表达式的递归限制不再是问题。

实时示例,嵌入调试。

这将基本上达到std::size_t您系统的限制。我已经用111111113.

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

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;  
}
于 2013-05-26T17:09:22.263 回答
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为在 2 时增加 1,在不是I2 时增加I2。这将导致新高大约是这个的两倍。

于 2013-05-26T16:30:28.317 回答
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
  • 42也是这些特殊素数的位(1<<2)+(1<<3)+(1<<5)
  • 545925250是 1 7 11 13 17 19 23 29 的位图,用于快速测试可被 2 3 5 整除
  • >>((x)%30&31)访问所述位图以进行所述快速测试¹
  • (x)<49(resp. (x)<2809and (x)<32761) 因为我们要告诉 7=√49 (resp. 53=√2809 and 181=√32761) prime
  • (x)%7&..&&(x)%47(x)%53&&..&&(x)%179因为我们测试可分性
  • 179因为下一个素数的平方超过 15 位。

如果宏是用来生成代码的,那是相当高效的。

在线尝试!


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

于 2020-05-19T16:23:47.137 回答