【问题标题】:Curious arithmetic error- 255x256x256x256=18446744073692774400奇怪的算术错误 - 255x256x256x256=18446744073692774400
【发布时间】:2012-09-12 21:39:06
【问题描述】:

我在c++下编程时遇到了一件奇怪的事情。这是一个简单的乘法。

代码:

unsigned __int64 a1 = 255*256*256*256;
unsigned __int64 a2= 255 << 24; // same as the above

cerr()<<"a1 is:"<<a1;
cerr()<<"a2 is:"<<a2;

有趣的是,结果是:

a1 is: 18446744073692774400 
a2 is: 18446744073692774400 

而它应该是:(使用计算器确认)

4278190080

谁能告诉我这怎么可能?

【问题讨论】:

  • 不明白为什么这获得了 8 票...
  • @H2CO3:因为它是一个“非常有趣的非显而易见的语言怪癖”,而不是因为这个问题得到了很好的表述或研究......
  • @H2CO3:是的,我们永远不要用我们 110% 不懂的语言编写任何代码。您确实意识到,不是吗,如果我们采取这种心态,我们将永远不会学到任何新东西?
  • 关于 C++ 编译器的一个恼人的事情是,在不打开警告的情况下使用它们会让你犯大量你不应该犯的错误。当我在 GCC 中使用 g++ -Wall -Wextra -pedantic 编译这段代码时,编译器非常清楚地告诉我:“警告:表达式 [-Woverflow] 中的整数溢出”。 Microsoft Visual Studio 也诊断出这个问题:“警告 C4307: '*' : 积分常数溢出”。我希望这可以作为将来编译警告的教训。
  • @DanielFischer:因此“取决于您的编译器”,因为每个人仍在忙于实施当前标准。 unsigned long long 不在 C++03 中,因此要使用它,您需要 C++03 的特定于编译器的扩展或(暂时)C++11 的特定于编译器的部分实现。实际上每个人都有long long

标签: c++ multiplication uint64


【解决方案1】:
 255*256*256*256

所有操作数都是int 你溢出了int。有符号整数的溢出是 C 和 C++ 中未定义的行为。

编辑:

请注意,如果您的int 类型为32-bit,则第二个声明中的表达式255 &lt;&lt; 24 也会调用未定义的行为。 255 x (2^24)4278190080 不能用 32-bit int 表示(最大值通常是 2147483647 上的 32-bit int 二进制补码表示)。

C 和 C++ 都对E1 &lt;&lt; E2 表示,如果E1 是有符号类型并且是正数,并且E1 x (2^E2) 不能以E1 的类型表示,则程序会调用未定义的行为。这里^ 是数学 power 运算符。

【讨论】:

  • 谢谢,它适用于 unsigned __int64 a1 = 255*256*256; a1=a1*256;
  • @user902383:那些 255 和 256 是整数。因此,C++ 使用整数(很可能是 32 位)完成所有中间步骤。在数值上,它以 0xFF000000 结束。将其转换为有符号的 64 位,您将得到 0xFFFFFFFFFF000000(符号扩展名)。这只是它可能发生的一种方式,但它是迄今为止最常见的一种。
  • 无论如何,256 保证具有int 类型,因为它适合int。将它乘以其他东西不会改变这一点,文字都很小。
  • @rubenvb: 是的,你不能根据标准争论,因为标准中文字的定义是一个简单的语法问题,256*255 不是:- )
  • @rubenvb 这是一个“常量表达式”,溢出会使程序格式错误。
【解决方案2】:

您的文字是int。这意味着所有的操作实际上都是在int上进行的,并及时溢出。这个溢出的值,当转换为无符号的 64 位 int 时,就是您观察到的值。

【讨论】:

  • OP 的根本错误比这个例子传播得更广:这是一个错误的假设,即 left-hand side 的类型以某种方式在右手散发着魔力侧面并使其透视改变行为。
  • @KerrekSB 来吧。自 8 位时代结束以来,人们根本不期望溢出,期望文字(或仅文字的表达式)被自动键入并不是不合理的。
  • @Potatoswatter:我认为如果表达式的 type 取决于其组成部分的 values,那将是相当疯狂的......你甚至指定? 256 * n是什么类型的?
  • @KerrekSB 因此,“只有文字。”但是通常(或良好的做法,恕我直言)将一个较大的常数指定为几个较小常数的乘积。这确实发生在现实生活中。
  • @Kerrek:我认为定义“仅涉及文字的表达式”并不难,并不比在 C++ 中定义“整数常量表达式”更难。但即使标准确实这样做了,也有人会想要另一种类型不同的特殊情况,并且规则会再次变得更加复杂。顺便说一句,在一种情况下,lhs 确实在 rhs 上散发出神奇的力量,即从重载名称中分配函数或成员函数指针。在特殊情况下,挖得足够深,甚至误导的假设也会成为指导;-)
【解决方案3】:

也许值得解释一下产生数字 18446744073692774400 的原因。从技术上讲,您编写的表达式会触发“未定义的行为”,因此编译器可能会产生 anything 作为结果;但是,假设 int 是 32 位类型,现在几乎总是这样,如果你写,你会得到同样的“错误”答案

uint64_t x = (int) (255u*256u*256u*256u);

并且该表达式确实不会触发未定义的行为。 (从unsigned intint 的转换涉及实现定义的 行为,但是由于多年来没有人生产过补码或符号和大小的CPU,所有实现您都可能遇到完全相同的方式定义它。)我已经用 C 风格编写了演员表,因为我在这里所说的一切都同样适用于 C 和 C++。

首先,让我们看看乘法。我用十六进制写右手边,因为这样更容易看到发生了什么。

255u * 256u               = 0x0000FF00u
255u * 256u * 256u        = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)

最后一个结果 0xFF000000u 具有 32 位数字集的最高位。因此,将该值转换为 signed 32 位类型会使其变为负数,就好像从中减去了 232 一样(这就是我上面提到的实现定义的操作)。

(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216

我在那里写了十六进制数字,没有u 后缀,以强调当您将其转换为有符号类型时,该值的位模式不会改变;它只是被重新解释。

现在,当您将 -16777216 分配给 uint64_t 变量时,它会通过添加 264 反向转换为无符号假象。 (与无符号到有符号的转换不同,此语义由标准规定。)这确实改变了位模式,将数字的所有高 32 位设置为 1 而不是 0预期:

(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u

如果你用十进制写 0xFFFFFFFFFF000000,你会得到 18446744073692774400。

作为最后一条建议,每当你从 C 或 C++ 中得到一个“不可能的”整数时,尝试以十六进制打印出来;以这种方式更容易看到二进制补码固定宽度算术的奇怪之处。

【讨论】:

  • 标准(C++03 中的 4.7.,在 C++11 中可能类似的地方;C99 和 C11 中的 6.3.1.3)规定转换为无符号类型以减少模 @987654332 @.
【解决方案4】:

答案很简单——溢出。

【讨论】:

    【解决方案5】:

    这里溢出发生在 int 上,当您将它分配给 unsigned int64 时,它转换为 18446744073692774400 而不是 4278190080

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-08
      • 2014-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-12
      • 2014-07-17
      相关资源
      最近更新 更多