【问题标题】:C++ int64 * double == off by oneC++ int64 * double == 减一
【发布时间】:2015-12-21 03:32:19
【问题描述】:

以下是我在 64 位环境和 32 位环境中测试过的代码。结果每次都精确地减少一个。预期结果是:1180000000,实际结果是 1179999999。我不知道究竟是为什么,我希望有人能教育我:

#include <stdint.h>
#include <iostream>

using namespace std;

int main() {
  double odds = 1.18;
  int64_t st = 1000000000;
  int64_t res = st * odds;
  cout << "result: " << res << endl;
  return 1;
}

感谢任何反馈。

【问题讨论】:

  • 你会遇到这种浮点问题。 double 是双精度,而不是无限精度!尝试cout &lt;&lt; setprecision(30) &lt;&lt; st * odds &lt;&lt; '\n' 以进一步了解正在发生的事情。
  • 谢谢MM!我现在试试看
  • MM 这是使用 setprecision():无精度:1179999999 精度:1180000000
  • 你为什么不发布你得到的结果?也许我们可以帮你算零:)

标签: c++


【解决方案1】:

1.18 或 118 / 100 不能用二进制精确表示,它会有重复的小数。如果你用十进制写 1 / 3,也会发生同样的情况。

那么让我们用十进制计算类似的情况,让我们计算(1 / 3) × 30000,当然应该是10000

  • 赔率 = 1 / 3st = 30000

    由于计算机的精度有限,我们必须将这个数字截断为有限的小数位数,比如 6,所以:

  • 赔率 = 0.333333

  • 0.333333 × 10000 = 9999.99。强制转换 (在您的程序中是隐式的) 会将这个数字截断为 9999

没有 100% 可靠的方法可以解决此问题。 floatdouble 只有有限的精度。处理这个是一个难题。

您的程序包含从doubleint64_t res = st * odds; 行上的整数的隐式转换。许多编译器会就此发出警告。它可能是您所描述类型的错误的来源。这个转换可以明确写成(int64_t) some_double,将数字四舍五入到零。

另一种方法是使用round(some_double); 舍入到最接近的整数。这将——在这种情况下——给出预期的结果。

【讨论】:

  • 您的最后一段不正确。 int64_t res = st * odds;int64_t res = (int64_t)(st * odds); 的行为被定义为完全相同 - 在这两种情况下,表达式 st * odds 都被转换为 int64_t,并且这种转换的行为是实现定义的如果乘法的结果在int64_t 中不能完全表示。
  • “你不应该隐式地将 double 转换为整数”是一个见仁见智的问题 - 我的观点是使用转换来隐藏警告也不理想
  • @M.M 是的,但有时您只需要将双精度数舍入为整数。我习惯总是明确地使用像floorround 这样的函数。
  • round 是一个好习惯。 floor(x+0.5) 有一些问题(它不适用于负数,当你达到一定数量级时它开始表现得很奇怪)
  • @M.M 关于大数的好点子。但是对于负数 floor(-1.9999 + 0.5) 按预期返回 -2.0,或者不是吗?
【解决方案2】:

首先 - 1.18double 中不能完全表示。数学结果:

double odds = 1.18;

1.17999999999999993782751062099(根据在线计算器)。

所以,从数学上讲,odds * st1179999999.99999993782751062099

但在 C++ 中,odds * st 是一个类型为 double 的表达式。所以你的编译器有两个选项来实现这个:

  • double 精度进行计算
  • 以更高的精度进行计算,然后将结果四舍五入到double

显然,在 IEEE754 中以 double 精度进行计算会得到精确的 1180000000

但是,以long double 精度执行此操作会产生更像1179999999.99999993782751062099 的结果

将其转换为double 现在是实现定义关于它是选择下一个最高值还是下一个最低值,但我相信通常选择下一个最低值.

然后将此次低结果转换为整数将截断小数部分。


这里有一个interesting blog post,作者描述了GCC的行为:

  • 它对 x86 代码使用 long double 中间精度(由于 x87 FPU 的 long double 寄存器)
  • 它使用 x64 代码的实际类型(因为 SSE/SSE2 FPU 更自然地支持这一点)

根据 C++11 标准,您应该能够通过从 &lt;cfloat&gt; 输出 FLT_EVAL_METHOD 来检查正在使用的中间精度。 0 表示实际值,2 表示正在使用 long double。

【讨论】:

  • 我不确定我的回答是否能完全说明问题; double d = st * odds; int64_t res = d; 产生与int64_t res = (double)(st * odds); 不同的结果(其中演员阵容是多余的,但具有说明性)。似乎(a)编译器直接从中间结果到int64,我认为标准不允许,或者(b)编译器正在进行编译时优化以计算res1,并且使用与相同事物的运行时计算不同的精度(我再次不确定标准是否允许这样做)
  • round() 似乎已经做到了,我已经修改了 'odds' 的值几次以确保它是一致的。感谢大家的意见和帮助!
猜你喜欢
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-29
  • 1970-01-01
  • 1970-01-01
  • 2013-03-07
  • 1970-01-01
相关资源
最近更新 更多