奇怪的算术错误-255x256x256x256=18446744073692774400



我在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

有人能告诉我这怎么可能吗?

255*256*256*256

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

编辑:

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

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

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

也许值得解释一下产生数字18446744073692774400的原因。从技术上讲,您编写的表达式会触发"未定义的行为",因此编译器可能会产生任何;然而,假设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位数字集中的最高位。因此,将该值强制转换为有符号的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++中得到一个"不可能"的整数时,试着用十六进制打印出来;通过这种方式,可以更容易地看到二的奇数与固定宽度算术的互补。

答案很简单——溢出。

此处int发生溢出,当您将其分配给无符号int64时,它转换为18446744073692774400,而不是4278190080

相关内容

  • 没有找到相关文章

最新更新