浮点计算给出的结果与使用双精度计算的结果不同

Floating point calculation gives different results with float than with double

本文关键字:计算 结果 双精度      更新时间:2023-10-16

我有以下代码行。

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()));
  • void onBeingHit(int decHP)方法接受整数并更新运行状况点。
  • float getDefensePercent()方法是一种获取方法,返回英雄的防御百分比。
  • ENEMY_ATTACK_POINT是一个宏观常数因子,定义为#define ENEMY_ATTACK_POINT 20

假设hero->getDefensePercent()返回0.1.所以计算是

20 * (1.0 - 0.1)  =  20 * (0.9)  =  18

每当我使用以下代码尝试时(没有附加f 1.0(

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

我得了17个。

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

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0f - hero->getDefensePercent()));

我得了18

这是怎么回事?尽管hero->getDefensePercent()已经处于浮动状态,但f重要吗?

这是怎么回事?为什么在这两种情况下整数结果都不18

问题是浮点表达式的结果在转换为整数值时四舍五入为零(在这两种情况下(。

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

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

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

如果您不知道二进制浮点数是如何从十进制数构造IEEE754那么如果它略小于或略大于您在代码中输入的十进制数,则几乎是随机的。所以你不应该指望这个。不要试图通过将f附加到其中一个数字并说"现在它可以工作,所以我把这个f留在那里"来解决这个问题,因为另一个值的行为再次不同。

为什么表达式的类型取决于此f的优先级?

这是因为 C 和 C++ 中的浮点文本默认为 double 类型。如果添加f,则为浮点数。浮点 epxression 的结果是"更大"类型。双精度表达式和整数的结果仍然是双精度表达式,int 和 float 将是浮点数。因此,表达式的结果要么是浮点数,要么是双精度数。

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

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

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

在C++11中,对此std::round()。在以前的标准版本中,没有这样的函数可以舍入到最接近的整数。(详情请参阅评论。

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

int round(double x) {
    return (x < 0.0) ? (x - .5) : (x + .5);
}

> 1.0 被解释为双精度值,而不是编译器将 1.0f 视为浮点数。

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

顾名思义,双精度精度是浮点数的 2 倍。通常,双精度具有 15 到 16 个十进制数字的精度,而浮点数只有 7

这种精度损失可能导致截断误差更容易浮出水面

请参阅 MSDN (C++(

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

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

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

请注意,在它之后添加 0.5 并截断 int 将导致结果四舍五入,因此当您的结果17.999...时,它将变为 18.499... ,这将被截断为 18