【问题标题】:Floating point calculation gives different results with float than with double浮点计算给出的结果与浮点数不同
【发布时间】:2013-01-31 04:00:51
【问题描述】:

我有以下代码行。

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()));
  • void onBeingHit(int decHP) 方法接受整数并更新健康点。
  • float getDefensePercent() 方法是一个 getter 方法,返回英雄的防御百分比。
  • 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 是否重要?

【问题讨论】:

  • 您无法准确保存.9.1(使用浮点数据类型)。双精度中较低的精度可能会导致类似18.00x 而不是17.999xxx。请注意,floating point --> int 始终处于倒置状态。
  • 不太了解 C++,但我希望您的文字被解释为 double,从而强制在 doubles 而不是 floats 中完成计算。
  • ENEMY_ATTACK_POINT 是一个整数。由于您使用的是整数数学,因此您会遇到舍入错误。

标签: c++ floating-point double floating-accuracy


【解决方案1】:

发生了什么事?为什么两种情况下都不是整数结果18

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

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

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

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

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

为什么表达式的类型取决于 f 的出现?

这是因为 C 和 C++ 中的浮点字面量默认为 double 类型。如果添加f,它是一个浮点数。浮点表达式的结果属于“更大”类型。双精度表达式和整数的结果仍然是双精度表达式,而 int 和浮点数将是浮点数。所以你的表达式的结果要么是浮点数,要么是双精度数。

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

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

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

在 C++11 中,有 std::round() 。在标准的早期版本中,there was no such function to round to the nearest integer.(详情请参见 cmets。)

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

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

【讨论】:

  • 感谢您的洞察力解释。
  • 您不必添加0.5,也不必编写自己的函数。只需使用std::round()(在&lt;cmath&gt;)。一般来说,我建议始终使用标准函数之一(roundfloorcieltrunc);这让你的意图变得清晰(并让你思考你真正想做的事情)。
  • @JamesKanze 只有 C++11。我相应地编辑了答案,谢谢。
  • @leemes 它也是 C99(和 Posix),因此存在于大多数 C++03 甚至 C++98 实现中。 Microsoft 可能是个例外,因为它们明确不支持现代 C。但是 Microsoft 或多或少地在支持 C++11 方面处于领先地位,因此任何最近的 Microsoft 编译器都应该支持它们。当然,既然它们也是 Posix,任何 Unix 编译器都应该有它们。
  • @JamesKanze 已修复。我不知道。但在 C++03 中,它不是 std::round,而只是 round。在答案中,我现在指的是 cmets。
【解决方案2】:

1.0 被解释为双精度,而 1.0f 被编译器视为浮点数。

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

顾名思义,double 的精度是 float 的 2 倍。一般来说,double 有 15 到 16 个十进制数字的精度,而 float 只有 7。

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

See MSDN (C++)

【讨论】:

  • double 的精度至少与 float 一样高。它是否具有相同、少于两次、两次或多于两次是实现定义的。在通常的 IEEE 格式中,double 具有 52 位精度,float 具有 24 位,double 53,因此 double 具有两倍以上的精度。但是有些处理器(不是大多数 C++ 程序员会看到它们)doublefloat 是相同的。 (还要注意,在大多数大型机上,机器浮点不是二进制的,而是基数 16 或基数 8。)
  • 另外(再次只是挑剔-您的答案确实没有错):小数精度的含义各不相同。对于往返(转换为十进制并返回,保证得到相同的值),使用 IEEE,您需要 9 (float) 或 17 (double) 位。或者,您可以考虑另一个方向的往返,给出 6 和 15。(这些对应于 numeric_limits 中的 digits10max_digits10。)
  • 没关系 :) 我喜欢新信息 :)
  • @JamesKanze 感谢您提供信息。
【解决方案3】:

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

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

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

请注意,添加0.5 并在其后立即截断为int 会导致结果四舍五入,因此当您的结果为17.999... 时,它将变为18.499...,它将被截断为@ 987654328@

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    • 2021-08-29
    相关资源
    最近更新 更多