C++ 浮点计算与双精度浮点计算得出的结果不同

C++ 浮点计算与双精度浮点计算得出的结果不同,c++,floating-point,double,floating-accuracy,C++,Floating Point,Double,Floating Accuracy,我有下面一行代码 hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent())); void onBeingHit(int decHP)方法接受整数并更新健康点 float getDefensePercent()方法是返回英雄防御百分比的getter方法 敌方攻击点是一个宏常量因子,定义为定义敌方攻击点20 假设hero->getDefensePercent()返回0.1。因此,计算是正确的 20

我有下面一行代码

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()));
  • void onBeingHit(int decHP)
    方法接受整数并更新健康点
  • float getDefensePercent()
    方法是返回英雄防御百分比的getter方法

  • 敌方攻击点
    是一个宏常量因子,定义为
    定义敌方攻击点20
假设
hero->getDefensePercent()
返回
0.1
。因此,计算是正确的

20 * (1.0 - 0.1)  =  20 * (0.9)  =  18
每当我尝试使用以下代码时(无
f
追加
1.0

我得了17分

但是对于以下代码(
f
附加在
1.0
之后)

我得了18分


发生什么事了?尽管
hero->getDefensePercent()
已处于浮点状态,但
f
是否有意义?

1.0被解释为双精度,而编译器将其视为浮点

f后缀只是告诉编译器哪个是浮点,哪个是双精度

顾名思义,double的精度是float的2倍。一般来说,双精度浮点型只有15到16位小数精度,而浮点型只有7位小数精度

这种精度损失可能导致截断错误更容易浮起


发生这种情况的原因是使用
double
时的结果更精确,即
1.0

尝试对结果进行四舍五入,这将在转换后得到更精确的积分结果:

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()) + 0.5);

请注意,添加
0.5
并立即截断为
int
将导致结果四舍五入,因此当您的结果为
17.999…
时,它将变成
18.499…
,这将被截断为
18
发生了什么事?为什么两种情况下的整数结果都不是
18

问题在于,当转换为整数值时(在这两种情况下),浮点表达式的结果向零舍入

0.1
不能精确表示为浮点值(在这两种情况下)。编译器将转换为二进制IEEE754浮点数,并决定是向上舍入还是向下舍入为可表示的值。然后,处理器在运行时将该值相乘,并对结果进行四舍五入以获得整数值

好,但是既然
double
float
都是这样,为什么在这两种情况中的一种情况下我得到
18
,而在另一种情况下得到
17
?我很困惑。

您的代码获取函数的结果,
0.1f
(浮点),然后计算
20*(1.0-0.1f)
,这是一个双表达式,而
20*(1.0f-0.1f)
是一个浮点表达式。现在,浮点版本恰好略大于
18.0
,并向下舍入到
18
,而双精度表达式略小于
18.0
,并向下舍入到
17

如果您不知道IEEE754二进制浮点数是如何由十进制数构造而成的,那么如果IEEE754二进制浮点数略小于或略大于您在代码中输入的十进制数,则它几乎是随机的。所以你不应该指望这个。不要试图通过在其中一个数字后面添加
f
,然后说“现在它可以工作了,所以我把这个
f
放在那里”来解决这样的问题,因为另一个值的行为又不同了

为什么表达式的类型取决于此
f

这是因为C和C++中的浮点文字是默认的类型<代码>双< />代码。如果添加

f
,则它是一个浮点。浮点表达式的结果是“更大”类型。双精度表达式和整数的结果仍然是双精度表达式,int和float的结果将是float。所以表达式的结果是浮点或双精度

好的,但我不想四舍五入为零。我想四舍五入到最近的数字。

若要解决此问题,请在将结果转换为整数之前将其添加一半:

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()) + 0.5);
在C++11中,有一种方法可以实现这一点。在本标准以前的版本中,(有关详细信息,请参见注释。)

如果没有
std::round
,可以自己编写。处理负数时要小心。转换为整数时,数字将被截断(向零舍入),这意味着负值将向上舍入,而不是向下舍入。因此,如果数字为负数,我们必须减去一半:

int round(double x) {
    return (x < 0.0) ? (x - .5) : (x + .5);
}
int轮(双x){
回报率(x<0.0)-(x-.5):(x+.5);
}

无法准确保存
.9
.1
(使用浮点数据类型)。可能双精度较低会导致类似
18.00x
的结果,而不是
17.999xxx
。请注意,<代码>浮点> int 总是被下移。不太了解C++,但是我希望您的文字被解释为“代码>双< /代码>,从而迫使计算在<代码>双< /代码> s而不是<代码>浮点< /COD> > S.<代码> SunyyAthAkcPoint < /Co> >是整数。由于使用整数数学,您会遇到舍入错误。感谢您的深入解释。您不必添加
0.5
,也不必编写自己的函数。只需使用
std::round()
(在
中)。一般来说,我建议始终使用标准功能之一(
round
floor
ciel
trunc
);这让你的意图变得清晰(并让你思考你真正想要做什么)。@JamesKanze它只是C++11。我对答案进行了适当的编辑,谢谢。@leems它也是C99(和Posix),因此在大多数C++03甚至C++98实现中都存在。微软可能是个例外
hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()) + 0.5);
int round(double x) {
    return (x < 0.0) ? (x - .5) : (x + .5);
}