我在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);
}
答案 0 :(得分:3)
简言之:
据我所知,std中没有tgamma
R本身作为choose
函数,所以我只会做下面的事情
R也有伽玛分布等,所以你也可以手工完成
为什么不打印factN
,factK
,factDiff
这些值?
简单的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
,或者为了更好地理解算法而暂时这样做,请注意计算组合的公式为:
k == 2
时,这简化为:
n*(n-1)/2
现在n
是偶数,或n-1
是偶数。您可以发现哪个,然后将该数字除以2,没有截断错误,然后将结果乘以不被除以2的数字。因此,您得到的确切结果不会出现截断错误,也不会中间溢出,仅使用积分算法。这是count_each_combination
使用的技术,但是推广到任何除数,如果它可以适合所提供的整数类型,则提供始终精确的结果。