浮点到整数转换出错(即使浮点数已经是整数)



我正在编写一个小函数来使用 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::roundBinCoeff 函数可能看起来像

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。 花车不能22那样,而是1.99999类似的东西。 因此,转换为 int 将删除小数部分。

因此,与其立即转换为 int,不如通过调用在cmathmath.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";
}

最新更新