N选择K功能崩溃Rcpp

时间:2014-07-28 22:13:50

标签: c++ r statistics combinations rcpp

我在C ++中编写了一个'n choose k'函数,它通过Rcpp与R连接。由于某种原因,我得到一个“除以零”的运行时错误。当我尝试评估30选择2时会发生这种情况。

我已经尝试手动评估每一行(使用evalCpp),我仍然对于除零的位置感到困惑。也许有人可以向我指出这一点或建议一个更好的方式来写n选择K?

以下是代码:

// [[Rcpp::export]]                                                                                                                                  
int chooseC(int n, int k) {                                                                                                                         
  if (k > n) {                                                                                                                                      
    std::cout << "Error. k cannot be greater than n." << std::endl;                                                                                 
    return 0;                                                                                                                                       
  }                                                                                                                                                 
  int factN = std::tgamma(n + 1);                                                                                                                   
  int factK = std::tgamma(k + 1);                                                                                                                   
  int factDiff = std::tgamma(n - k + 1);                                                                                                            
  return factN/(factK*factDiff);                                                                                                                    
} 

2 个答案:

答案 0 :(得分:3)

简言之:

  • 据我所知,std中没有tgamma

  • R本身作为choose函数,所以我只会做下面的事情

  • R也有伽玛分布等,所以你也可以手工完成

  • 为什么不打印factNfactKfactDiff这些值?

简单的Rcpp解决方案:

#include <Rcpp.h>

// [[Rcpp::export]]  
double chooseC(double n, double k) {
  return Rf_choose(n, k);
}

示例:

R> chooseC(5,2)     
[1] 10
R> 

编辑:在@Blastfurnace关于C ++ 11 tgamma()标题中的cmath的评论之后,这是一个修复后的版本,对我来说很好:

#include <Rcpp.h>
#include <cmath>

// [[Rcpp::plugins(cpp11)]]

// [[Rcpp::export]] 
int chooseCtake2(int n, int k) {
  if (k > n) {
    Rcpp::stop("Error. k cannot be greater than n.");
  }
  int factN = std::tgamma(n + 1);
  int factK = std::tgamma(k + 1);
  int factDiff = std::tgamma(n - k + 1);
  return factN/(factK*factDiff); 
}

使用示例:

R> sourceCpp("/tmp/chooseC.cpp")
R> chooseCtake2(2,3)
Error: Error. k cannot be greater than n.
R> chooseCtake2(5,2)
[1] 10
R> 

答案 1 :(得分:3)

所以std::tgamma(x)计算x的伽玛函数。这个功能非常迅速地进入无穷大:

http://www.wolframalpha.com/share/clip?f=d41d8cd98f00b204e9800998ecf8427et5pmak8jtn

在x == 31时,你的数字非常大。

将这个非常大的double转换回int时,结果是未定义的行为(4.9 Floating-integral conversions [conv.fpint]):

  

浮点类型的prvalue可以转换为a的prvalue   整数类型。转换转发;也就是说,小数部分   被丢弃了。如果截断值不能,则行为未定义   用目的地类型表示。

在我的系统上,此转换(输入为{30,2})会产生一个值为-2147483648的int。通过插入一些打印语句可以很容易地观察到这一点:

int
chooseC(int n, int k)
{
    if (k > n)
    {                                                                                                                                      
        std::cout << "Error. k cannot be greater than n.\n";
        return 0;                                                                                                                                       
    }                                                                                                                                                 
    int factN = std::tgamma(n + 1);
    std::cout << "factN = " << factN << '\n';
    int factK = std::tgamma(k + 1);
    std::cout << "factK = " << factK << '\n';
    int factDiff = std::tgamma(n - k + 1);
    std::cout << "factDiff = " << factDiff << '\n';
    std::cout << "factK*factDiff = " << factK*factDiff << '\n';
    return factN/(factK*factDiff); 
}

对我而言输出:

factN = -2147483648
factK = 2
factDiff = -2147483648
factK*factDiff = 0

可以看出,UB最终导致除以零,也就是UB。听起来与您所看到的行为非常相似。

这个问题的解决方案是仅使用积分算法计算事物,并且如果最终结果可以在整数类型中表示,则中间计算不会溢出。这需要使用最大公约数函数。

开源代码可以在这里找到:

http://howardhinnant.github.io/combinations.html

搜索&#34; count_each_combination&#34;。您的chooseC可以按count_each_combination编码,如下所示:

int
chooseC(int n, int k)
{
    if (k > n)
    {                                                                                                                                      
        std::cout << "Error. k cannot be greater than n.\n";
        return 0;                                                                                                                                       
    }                                                                                                                                                 
    return count_each_combination(n-k, k);
}

现在chooseC(30, 2)将返回435.如果count_each_combination无法将结果存储在int中,则会引发std::overflow_error

如果您想将chooseC限制为k == 2,或者为了更好地理解算法而暂时这样做,请注意计算组合的公式为:

enter image description here

k == 2时,这简化为:

n*(n-1)/2

现在n是偶数,或n-1是偶数。您可以发现哪个,然后将该数字除以2,没有截断错误,然后将结果乘以不被除以2的数字。因此,您得到的确切结果不会出现截断错误,也不会中间溢出,仅使用积分算法。这是count_each_combination使用的技术,但是推广到任何除数,如果它可以适合所提供的整数类型,则提供始终精确的结果。