【问题标题】:confuse in "double to long long" in c++在 C++ 中混淆“double to long long”
【发布时间】:2013-10-08 14:07:26
【问题描述】:

我的代码:

int main()
{
long long a = pow(2,63) - 1;
long long b = pow(2,63);
double c  = pow(2,63) - 1;
double d = pow(2,63);
printf("%lld %lld \n%f %f \n%lld %lld\n", a, b, c, d, (long long)c, (long long)d);

return 0;
}

并且执行结果是(在win7 x64中使用gcc的代码块):

9223372036854775807 9223372036854775807
9223372036854775800.000000 9223372036854775800.000000
-9223372036854775808 -9223372036854775808

问题:

为什么是a == b?

我知道c == d 因为double 的精度。

但是为什么(long long)c 和(long long)d 不是9223372036854775800?

为什么是(long long)c != a 和(long long)d != b?

【问题讨论】:

  • pow 返回 double,而不是整数。
  • 不应该是 %lf 而不是 %f 在你的格式字符串打印双打?
  • @SchighSchagh:不,%lf 是为long double 服务的; %f 用于double(还有float,在作为可变参数传递时提升为double)。
  • @MikeSeymour 嗯,看起来你对浮动提升加倍是正确的,但事实证明 %f 和 %lf 都适用于 float 和 double,而你实际上将%Lf 用于long double。顺便说一句,对于scanf,您确实需要将%f 仅用于float,而%lf 仅用于double。 题外话>

标签: c++ double long-long


【解决方案1】:

pow(2,63) - 1 全部以双精度浮点运算完成。特别是,-1 被转换为 -1.0,这太小了

【讨论】:

  • 这就是为什么c == d (作者已经知道) - 但它没有解释其余部分。有关详细信息,请进一步查看我的答案。
  • @tenos:喝了一杯大海的水(2^63-1),你期待大海的重量发生变化吗?
【解决方案2】:

为什么是a == b?我知道c == d 因为双精度。

出于完全相同的原因。整数类型没有pow 的重载,因此使用double 完成算术运算。由于double 通常有 52 位有效位,因此对 263 这样大的值加或减 1 将无效。

为什么(long long)c 和(long long)d 不是9223372036854775800?

因为long long是64位有符号类型,最大可表示值为263-1。 c 和 d 可能都具有超出范围的值 263(或什至稍大的值)。正如您所观察到的,在典型的 2s 补码平台上,这可能会溢出以给出大约 -263 的值。但请注意,这是未定义的行为;如果浮点转换溢出,你就不能依赖任何东西。

为什么是(long long)c != a 和(long long)d != b?

我不知道;对我来说,a 和 b 具有相同的大负值。看起来您的实现的一些怪癖导致 a 和 b 以值 263-1 而不是预期的 263 结束。在处理浮点数时,与往常一样,您应该预料到类似的小舍入误差。

你可以通过整数运算得到准确的结果:

long long a = (1ULL << 63) - 1;
unsigned long long b = 1ULL << 63;

注意使用无符号算术,因为如上所述,有符号的(1LL &lt;&lt; 63) 会溢出。

【讨论】:

  • “如您所见,在典型的 2s 补码平台上,这将溢出并给出 -2^63。”此溢出是未定义的行为,不会导致 OP 平台上的 -2^63(对于 a 和 b)。编译器不必在a 和(long long)c 之间给出一致的结果。
  • @PascalCuoq:确实;我添加了一些额外的限定条件,以防其他人将我对观察到的行为的可能原因的描述误认为是任何类型的定义。
【解决方案3】:

为什么 a == b

因为您的编译器 (gcc) 计算了用于初始化 a 和 b 的值,并且发现(证明?)两者都匹配或超过了 long long 的最大可能值,所以它都使用该值进行了初始化最大值LLONG_MAX(或0x7FFFFFFFFFFFFFFF,或9223372036854775807在您的平台上)。

请注意(正如 Pascal Cuoq 所指出的)这是未定义的行为,由在初始化 a 和 b 时将 double 转换为 long long 时溢出引起的。虽然 gcc 如上所述处理此问题,但其他编译器可以以不同方式处理此问题

由于 double 的精度,我知道 c ==d

c 和 d 保持相同值的原因确实是因为 double 的精度:

  • pow(2, 63) 可以用分数1 和指数63 准确表示
  • pow(2, 63) - 1 无法准确表示

它没有显示9223372036854775808(存储在c 和d 中的精确值)的原因是printf 精度,它在您的平台上显然只显示17 位数字。您可以使用例如强制它显示更多内容。 %20.0f,但在 Windows 上可能不会因为 this bug 而有所作为。

为什么 (long long)c 和 (long long)d 不是 9223372036854775800 ?

因为c 和d 保存值9223372036854775808 或0x8000000000000000,当打印为有符号值时变为-9223372036854775808。

请注意,这又是未定义的行为(由于有符号溢出)。

为什么是 (long long)c != a 和 (long long)d != b?

因为它们的计算方式不同。 a 和 b 由编译器计算,而 (long long) c 和 (long long) d 在运行时计算。

虽然通常情况下,这些不同的计算方式应该产生相同的结果,但我们在这里处理的是未定义的行为(如前所述),所以一切正常。在您的情况下,编译器的结果与运行时结果不同。

【讨论】:

  • a 和b 的初始化都会调用未定义的行为(从双精度类型到整数类型的转换中溢出)。它们不必是 0x7FFFFFFFFFFFFFFF,也不必是相同的值。您听起来好像这些变量接收这些值是正常的,但与在int t, u; *(&amp;t + 1) = 1; 中将u 设置为1 相比,这并不值得期待。
  • @Sander printf("20.0f",c) 与 printf("%f",c) 的结果相同
  • @PascalCuoq :这是 gcc 的行为 - 提问者使用的编译器 - 这就是为什么我说“你的编译器......”。
  • @tenos :我似乎记得 Windows C 运行时有这个问题......我似乎记得这是一个已知错误(即它只能显示 17 位精度),但是对 Windows 环境有更深入了解的人将能够确认/否认这一点。
【解决方案4】:

因为pow 返回双精度和双精度丢失。这就是为什么a==b。

【讨论】:

  • 为什么(long long)c != a and (long long)d != b ?
  • 因为你可以看到他们的标志是不同的。如果您不计算第一个数字(即符号),它们应该是相同的。
【解决方案5】:

pow(2, 63) 等价于pow((double) 2, (double) 63)。

确实,C++11 26.8 [c.math] 第 3 段说 &lt;cmath&gt; 提供了 double pow(double, double) 的声明,第 11 段说(强调我的)

  1. 如果与 double 形参对应的任何实参具有 long double 类型,则与 double 形参对应的所有实参都将有效地转换为 long double。
  2. 否则,如果与double 参数对应的任何参数的类型为double 或整数类型,则与double 参数对应的所有参数都将有效地转换为double。
  3. 否则,对应于双精度参数的所有参数都将有效地转换为浮点数。

现在,2 和 63 是 ints,因此,pow(2, 63) 等价于 pow((double) 2, (double) 63)。然后返回类型为double,它不需要“查看”2^63 和2^63 - 1 之间的差异所需的 63 位精度。

我推荐阅读 this post 和 Howard Hinnant 的出色回答。

【讨论】:

    【解决方案6】:

    long long -> %lld

    long double ->%Lf

    双 -> %f

    浮点数 -> %f

    int -> %d

    阅读>中的第15章了解更多细节。

    【讨论】:

    • 1) 问题中的程序正确使用了printf。 2) 您对printf 的建议是错误的,其中%f 需要double(这可能是由默认参数提升引起的)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-23
    • 2014-09-30
    • 2017-12-22
    • 1970-01-01
    • 1970-01-01
    • 2015-10-18
    相关资源
    最近更新 更多