Why does long long n = 2000*2000*2000*2000; overflow?

long long int n = 2000*2000*2000*2000;    // overflow


long long int n = pow(2000,4);            // works
long long int n = 16000000000000;         // works

Why does the first one overflow (multiplying integer literal constants to assign to a long long)?

What's different about it vs. the second or third ones?

12109 次浏览

2000*2000*2000*2000是4个 int值的乘法运算,返回一个 int值。当您将这个 int值赋给 long long int n时,溢出已经发生了(如果 int是32位,则产生的值将不适合)。

您需要确保不会发生溢出,因此当您编写

long long int n = static_cast<long long int>(2000)*2000*2000*2000;

确保执行的是 long long int乘法(long long int乘以 int将返回 long long int,因此在您的情况下不会出现溢出)。

一个更短(也更好)的方法是编写 2000LL2000ll而不是 static_cast。为整数文字提供了正确的类型。这是不需要的2000年,它适合一个 int,但它将需要更高的价值,不适合一个 int

long long int n = 2000LL*2000*2000*2000;
long long int n = 2000LL*2000LL*2000LL*2000LL;

因为 2000是一个 int,通常是32位的。只要使用 2000LL

使用 LL后缀代替 ll是由@AdrianMole 建议的,现在已经删除,注释。请检查他的 回答

默认情况下,整数文本是可以保存其值但不小于 int的最小类型。2000可以很容易地存储在 int 中,因为 Standard 保证它至少是一个16位类型。

算术运算符总是以存在的较大但不小于 int的类型调用:

  • char*char将被提升到 operator*(int,int)->int
  • char*int呼叫 operator*(int,int)->int
  • long*int呼叫 operator*(long,long)->long
  • int*int仍然调用 operator*(int,int)->int

至关重要的是,类型不依赖于结果是否可以存储在推断的类型中。这正是在您的情况下发生的问题-乘法是用 int完成的,但是结果溢出,因为它仍然存储为 int

C + + 不支持像 Haskell 那样基于目的地推断类型,因此赋值是不相关的。

第一种是使用整数(通常是32位)的乘法。它溢出是因为这些整数不能存储 2000^4。然后将结果强制转换为 long long int

第二个函数调用 pow 函数,该函数将第一个参数强制转换为 double并返回 double。然后将结果强制转换为 long long int。在这种情况下没有溢出,因为数学运算是在一个双重值上完成的。

第一行代码的 RHS 上的常量(文字)是 int值(没有long long int)。因此,乘法是使用 int算法执行的,这将溢出。

为了解决这个问题,可以使用 LL后缀将常量设置为 long long:

long long int n = 2000LL * 2000LL * 2000LL * 2000LL;

首选

事实上,正如 Peter Cordes在注释中指出的,LL后缀实际上只是第一个常量(最左边)或第二个常量上的 需要。这是因为,当两个不同 队伍的类型相乘时,低秩的操作数被提升为高秩的类型,如下所述: C + + 运算符中的隐式类型转换规则。此外,由于 *(乘法)操作符具有 从左到右的联想性,所以第一次乘法的“升级”结果将该升级传播到第二次和第三次。

因此,以下两条线路中的任何一条也可以在不溢出的情况下工作:

long long int n1 = 2000LL * 2000 * 2000 * 2000;
long long int n2 = 2000 * 2000LL * 2000 * 2000;

注意: 虽然小写后缀(如在 2000ll中)是有效的 C + + ,并且完全明确的 到编译器,但是在 longlong long整数文字中,有一个 普遍共识,小写字母‘ ell’应该避免,因为它很容易被误认为数字 1被人类读者。因此,您将注意到,在本文提供的答案中,一直使用了 2000LL(大写后缀)。

其他答案(截至本文写作时)似乎不够明确,不足以回答所述的问题。我会尽力填补这个空白。

为什么第一个溢出(将整数常量乘以赋值给一个长长的整数常量) ?

表达方式

long long int n = 2000*2000*2000*2000;

评估结果如下:

long long int n = ((2000*2000)*2000)*2000;

步骤在哪里(假设是32位 int) :

  1. (2000*2000)是两个 int值的乘法,产生4000000,这是另一个 int值。
  2. ((2000*2000)*2000)是上述产生的 int值4000000与 int值2000的乘法。如果该值能够适合 int,那么它将产生800000000。但是我们假设的32位 int 可以存储231-1 = 2147483647的最大值。所以我们现在就有溢出了。
  3. 如果上面没有溢出,下一个乘法就会发生。
  4. 将生成的 int乘积分配给 long long变量(如果不是溢出的话) ,这将保留该值。

因为我们确实有溢出,语句有未定义行为,所以步骤3和4不能得到保证。

它与第二个或第三个有什么不同?

  • long long int n = pow(2000,4);

pow(2000,4)20004转换成 double(参见 pow上的一些文件) ,然后函数实现尽最大努力产生一个很好的结果近似值,作为 double。然后赋值将此 double值转换为 long long

  • long long int n = 16000000000000;

文字 16000000000000太大,无法放入 int中,因此它的类型是下一个可以放入值的有符号类型。它可以是 longlong long,取决于平台。详情请参阅 字面值 # 字面值的类型。然后赋值将这个值转换为 long long(或者只是写入它,如果文字的类型已经是 long long)。

您可能需要在 C + + 中使用以下内容来理解这一点:

#include<iostream>
#include<cxxabi.h>


using namespace std;
using namespace abi;


int main () {
int status;
cout << __cxa_demangle(typeid(2000*2000*2000*2000).name(),0,0,&status);
}

如您所见,类型是 int

在 C 语言中,可以使用(承蒙) :

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>


#define typename(x) _Generic((x),        /* Get the name of a type */             \
\
_Bool: "_Bool",                  unsigned char: "unsigned char",          \
char: "char",                     signed char: "signed char",            \
short int: "short int",         unsigned short int: "unsigned short int",     \
int: "int",                     unsigned int: "unsigned int",           \
long int: "long int",           unsigned long int: "unsigned long int",      \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
float: "float",                         double: "double",                 \
long double: "long double",                   char *: "pointer to char",        \
void *: "pointer to void",                int *: "pointer to int",         \
char(*)[]: "pointer to char array",      default: "other")




unsigned int a = 3;
int main() {
printf("%s", typename(a-10));
return 0;
}

这里表达式的类型是 unsigned int,因为类型不匹配隐式地将类型升级到 unsigned intint之间的最大类型,即 unsigned int。该 unsigned int将下流到一个大的正面,这将是预期的负面时,分配或解释为一个 int。无论所涉及的值是什么,计算的结果总是 unsigned int

C

没有后缀的整数字面值的最小默认类型是 int,但是只有当字面值超过这个值时,它的类型才会变成 unsigned int; 如果大于这个值,它的类型就是 long int,因此2000都是 int。然而,对文字执行的 表情类型,使用一元或二进制运算符,使用隐式类型层次结构来决定类型,而不是结果的值(不像文字本身使用文字的长度来决定类型) ,这是因为 C 使用类型强制,而不是类型合成。为了解决这个问题,必须在2000年代使用长后缀 ul来显式指定文字的类型。

类似地,十进制文字的默认类型是 double,但是可以使用 f后缀更改它。前缀不更改十进制或整数字面值的类型。

字符串文字的类型是 char [],虽然它实际上是一个 const char [],并且只是字符串文字在 .rodata中实际表示的第一个字符的地址,地址可以像任何数组一样使用一元符号 &"string",这是相同的值(地址)作为 "string",只是一个不同的类型(char (*)[7]char[7]; "string"char[]不只是(在编译器级别)一个指向数组的指针,它 .rodata5的数组,而一元符号提取只是指向数组的指针)。u前缀将其改为 const char []0数组,即 const char []1; const char []2前缀将其改为 const char []3数组,即 const char []4; const char []5前缀将其改为 const char []6数组,即 const char []7。const char []8是一个 const char []9,无前缀字符串使用实现特定的编码,这通常与 const char []8相同,即 UTF-8,其中 ASCII 是一个子集。只能用于字符串文字的 .rodata6(只能在 GNU C (.rodata2以后)上使用)可以使用前缀,即 .rodata3或 .rodata4,但这不影响类型。

字符文字的类型是 int,除非前缀是 u(u'a'unsigned short int)或 U(U'a'unsigned int)。在字符文字上使用时,u8L都是 int。字符串或字符文字中的转义序列不影响编码,因此也不影响类型,它只是实际上将要编码的字符呈现给编译器的一种方式。

复数字面值 10i+110j+1的类型是 complex int,其中实部和虚部都可以有一个后缀,如 10Li+1,在这种情况下,使虚部变长,整体类型是 complex long int,并升级实部和虚部的类型,所以不管你把后缀放在哪里或者是否放在两者上。不匹配总是使用两个后缀中最大的一个作为整体类型。

使用显式强制转换而不使用文字后缀,如果你正确地使用它,并且意识到它截断/扩展的语义差异,那么总会产生正确的行为(符号扩展为 signed; 零扩展为 unsigned——这是基于被强制转换的文字或表达式的类型,而不是被强制转换的类型,所以 signed int是符号扩展为 unsigned long int)一个文字到该类型的表达式,而不是本身具有该类型的文字。

C + +

enter image description here

同样,最小默认类型是最小文本基的 int。文字基础,即文字的实际值,后缀影响最终的文字类型,根据下表,在每个框中的每个后缀,最终类型的顺序根据实际文字基础的大小从最小到最大列出。对于每个后缀,文字的最终类型只能等于或大于后缀类型,并且基于文字基的大小。C 表现出同样的行为。当大于 long long int时,根据编译器的不同,使用 __int128。我认为您还可以创建自己的字面后缀操作符 i128并返回该类型的值。

十进制文字的默认类型与 C 相同。

字符串文字的类型是 char []&"string"的类型是 const char (*) [7]+"string"的类型是 const char *(在 C 中,你只能使用 "string"+0衰变)。C + + 的不同之处在于后两种形式获得了 const,而 C 语言没有。字符串前缀的行为与 C 中相同

字符和复杂文字的行为与 C 相同。