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

Ham*_*ani 25 c++ multiplication uint64

我在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;
Run Code Online (Sandbox Code Playgroud)

有趣的是结果是:

a1 is: 18446744073692774400 
a2 is: 18446744073692774400 
Run Code Online (Sandbox Code Playgroud)

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

4278190080
Run Code Online (Sandbox Code Playgroud)

谁能告诉我怎么可能呢?

oua*_*uah 39

 255*256*256*256
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

C和C++都表示E1 << E2,如果E1是有符号类型且为正且E1 x (2^E2)无法在类型中表示E1,则程序将调用未定义的行为.这^是数学运算符.

  • @ user902383:那些255和256是整数.所以C++使用整数(很可能是32位)完成所有中间步骤.在数字上,它最终以0xFF000000结束.将其转换为带符号的64位,然后得到0xFFFFFFFFFF000000(符号扩展名).这只是它可能发生的一种方式,但它是迄今为止最常见的一种方式. (6认同)
  • @rubenvb:是的,你不能认为基于标准,因为标准中文字的定义是一个简单的语法问题,而"256*255"不是它:-) (2认同)

Pup*_*ppy 17

你的文字是int.这意味着所有操作都实际执行int,并立即溢出.转换为无符号64位int时,此溢出值是您观察到的值.

  • OP的根本错误是比这个例子更广泛的传播:误导的假设是*左手边的类型*以某种方式在右手边散发出魔力,并使其彻底改变行为. (6认同)
  • @Kerrek:我认为定义"只涉及文字的表达式"并不困难,比在C++中定义"整数常量表达式"更困难.但即使标准确实如此,有人也会想要另一种类型不同的特殊情况,规则会再次变得更加复杂.顺便说一句,在一种情况下,lhs确实在rhs上散发出神奇的力量,它从一个重载的名称中分配一个函数或成员函数指针.深入挖掘甚至误导的假设可以在特殊情况下得到指导;-) (5认同)
  • @KerrekSB来吧.自8位时代结束以来,人们根本不期望溢出,并且期望自动输入文字(或仅文字的表达式)并不是不合理的. (4认同)

zwo*_*wol 16

或许有必要解释生成数字18446744073692774400的情况.从技术上讲,您编写的表达式会触发"未定义的行为",因此编译器可能会产生任何结果; 但是,假设int是32位类型,现在几乎总是如此,如果你写的话,你会得到相同的"错误"答案

uint64_t x = (int) (255u*256u*256u*256u);
Run Code Online (Sandbox Code Playgroud)

那表情也没有引发未定义行为.(从转换unsigned intint涉及实现定义的行为,但由于没有人在多年内产生一个补码或符号和大小的CPU,你可能遇到的所有实现都以完全相同的方式定义它.)我写了用C风格演员,因为我在这里说的一切同样适用于C和C++.

首先,让我们来看看乘法.我正在以十六进制编写右侧,因为它更容易看到正在发生的事情.

255u * 256u               = 0x0000FF00u
255u * 256u * 256u        = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)
Run Code Online (Sandbox Code Playgroud)

最后一个结果,0xFF000000u具有32位数字集的最高位.因此,将该值转换为带符号的 32位类型会导致它变为负数 - 如果从中减去了32 32(这是我上面提到的实现定义的操作).

(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216
Run Code Online (Sandbox Code Playgroud)

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

现在,当您将-16777216分配给uint64_t变量时,通过添加2 64将其反转换为无符号as-if .(与无符号到符号的转换不同,此语义由标准规定.)这确实改变了位模式,将数字的所有高32位设置为1而不是0,如您所料:

(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u
Run Code Online (Sandbox Code Playgroud)

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

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