我正在编写一个小函数来使用 c++ 提供的 tgamma 函数来计算二项式系数。 tgamma 返回浮点值,但我想返回一个整数。请看一下这个示例程序,比较三种将浮点数转换回 int 的方法:
#include <iostream>
#include <cmath>
int BinCoeffnear(int n,int k){
return std::nearbyint( std::tgamma(n+1) / (std::tgamma(k+1)*std::tgamma(n-k+1)) );
}
int BinCoeffcast(int n,int k){
return static_cast<int>( std::tgamma(n+1) / (std::tgamma(k+1)*std::tgamma(n-k+1)) );
}
int BinCoeff(int n,int k){
return (int) std::tgamma(n+1) / (std::tgamma(k+1)*std::tgamma(n-k+1));
}
int main()
{
int n = 7;
int k = 2;
std::cout << "Correct: " << std::tgamma(7+1) / (std::tgamma(2+1)*std::tgamma(7-2+1)); //returns 21
std::cout << " BinCoeff: " << BinCoeff(n,k); //returns 20
std::cout << " StaticCast: " << BinCoeffcast(n,k); //returns 20
std::cout << " nearby int: " << BinCoeffnear(n,k); //returns 21
return 0;
}
为什么,即使计算返回等于 21 的浮点数,"正常"转换也会失败,只有 nearint 返回正确的值。实现这一点的最佳方法是什么?
编辑:根据C ++文档,tgamma(int(返回一个双精度值。
从这个std::tgamma
参考:
如果 arg 是自然数,
std::tgamma(arg)
是 arg-1 的阶乘。如果参数是足够小的整数,则许多实现会计算确切的整数域阶乘。
似乎您正在使用的编译器正在这样做,计算表达式std::tgamma(7+1)
的7
阶乘。
结果可能因编译器而异,也可能因优化级别而异。正如 Jonas 所证明的那样,优化和未优化的构建之间存在很大差异。
@nos 的这句话说得很中肯。请注意,第一行
std::cout << "Correct: " <<
std::tgamma(7+1) / (std::tgamma(2+1)*std::tgamma(7-2+1));
打印double
值,但不执行浮点到整数的转换。
浮点计算的结果确实小于 21,但这个double precision
值被cout
打印为 21。
在我的机器上(x86_64,gnu libc,g++ 4.8,优化级别 0(,设置cout.precision(18)
使结果明确。
Correct: 20.9999999999999964 BinCoeff: 20 StaticCast: 20 nearby int: 21
在这种情况下,用浮点运算替换整数运算是可行的,但必须记住结果必须是整数。目的是使用std::round
.
std::nearbyint
的问题在于,根据舍入模式的不同,它可能会产生不同的结果。
std::fesetround(FE_DOWNWARD);
std::cout << " nearby int: " << BinCoeffnear(n,k);
将返回 20。
因此,std::round
BinCoeff 函数可能看起来像
int BinCoeffRound(int n,int k){
return static_cast<int>(
std::round(
std::tgamma(n+1) /
(std::tgamma(k+1)*std::tgamma(n-k+1))
));
}
浮点数具有与之关联的舍入错误。这里有一篇关于这个主题的好文章:每个计算机科学家都应该知道的关于浮点运算的知识。
在您的情况下,浮点数的值非常接近但小于21
。隐式浮积分转换的规则说:
数部分被截断,即小数部分是 丢弃。
而std::nearbyint
:
使用当前舍入模式将浮点参数 arg 舍入为浮点格式的整数值。
在这种情况下,浮点数将正好21
,以下隐式转换将返回21
。
前cout
输出21
,因为默认情况下在cout
中发生舍入。请参阅std::setprecition
。
这是一个活生生的例子。
实现这一点的最佳方法是什么?
使用获取并返回unsigned int
而不是tgamma
的精确整数阶乘函数。
问题在于处理floats
。 花车不能2
像2
那样,而是1.99999
类似的东西。 因此,转换为 int 将删除小数部分。
因此,与其立即转换为 int,不如通过调用在cmath
或math.h
中声明的ceil
函数 w/c 来将其转换为 int。
此代码将返回所有 21
个#include <iostream>
#include <cmath>
int BinCoeffnear(int n,int k){
return std::nearbyint( std::tgamma(n+1) / (std::tgamma(k+1)*std::tgamma(n-k+1)) );
}
int BinCoeffcast(int n,int k){
return static_cast<int>( ceil(std::tgamma(n+1) / (std::tgamma(k+1)*std::tgamma(n-k+1))) );
}
int BinCoeff(int n,int k){
return (int) ceil(std::tgamma(n+1) / (std::tgamma(k+1)*std::tgamma(n-k+1)));
}
int main()
{
int n = 7;
int k = 2;
std::cout << "Correct: " << (std::tgamma(7+1) / (std::tgamma(2+1)*std::tgamma(7-2+1))); //returns 21
std::cout << " BinCoeff: " << BinCoeff(n,k); //returns 20
std::cout << " StaticCast: " << BinCoeffcast(n,k); //returns 20
std::cout << " nearby int: " << BinCoeffnear(n,k); //returns 21
std::cout << "n" << (int)(2.9995) << "n";
}