如何解决为什么long long n = 2000*2000*2000*2000;溢出?
long long int n = 2000*2000*2000*2000; // overflow
long long int n = pow(2000,4); // works
long long int n = 16000000000000; // works
为什么第一个溢出(乘以整数文字常量以分配给 long long)?
它与第二个或第三个有什么不同?
解决方法
因为 2000
是一个 int
,通常是 32 位。只需使用 2000LL
。
@AdrianMole 在评论中建议使用 LL
后缀代替 ll
。请检查他的answer。
默认情况下,整数文字是可以保存其值但不小于 int
的最小类型。 2000
可以很容易地存储在 int 中,因为标准保证它至少是一个有效的 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 那样基于目的地推断类型,因此赋值无关紧要。
,第一行代码的 RHS 上的常量(文字)是 int
值(不是 long long int
)。因此,乘法是使用 int
算法执行的,这会溢出。
要解决此问题,请使用 long long
后缀使常量 LL
:
long long int n = 2000LL * 2000LL * 2000LL * 2000LL;
事实上,正如 Peter Cordes 的评论中所指出的,LL
后缀实际上只是需要在第一个(最左边)或第二个常量上。这是因为,当两个不同的 ranks 类型相乘时,较低等级的操作数会提升为较高等级的类型,如下所述:Implicit type conversion rules in C++ operators。此外,由于 *
(乘法)运算符具有 left-to-right associativity,第一次乘法的“提升”结果将该提升传播到第二次和第三次。
因此,以下任一行也不会溢出:
long long int n1 = 2000LL * 2000 * 2000 * 2000;
long long int n2 = 2000 * 2000LL * 2000 * 2000;
注意:尽管小写后缀(如 2000ll
中的)是有效的 C++,并且对于编译器来说完全明确,但是有一个 general consensus在 long
和 long long
整数文字中应该避免使用小写字母 'ell',因为它很容易被人类读者误认为是数字 { {1}}。因此,您会注意到此处提供的所有答案都使用了 1
(大写后缀)。
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
,所以在你的情况下没有溢出)。
更短(更好)的方法是写 2000LL
或 2000ll
而不是 static_cast
。这为整数文字提供了正确的类型。这对于适合 int
的 2000 不需要,但对于不适合 int
的更高值则需要。
long long int n = 2000LL*2000*2000*2000;
long long int n = 2000LL*2000LL*2000LL*2000LL;
,
其他答案(截至撰写本文时)似乎不够明确,无法回答所述问题。我会努力填补这个空白。
为什么第一个溢出(乘以整数文字常量以分配给 long long)?
表达式
long long int n = 2000*2000*2000*2000;
评估如下:
long long int n = ((2000*2000)*2000)*2000;
步骤在哪里(假设 32 位 int
):
-
(2000*2000)
是两个int
值的乘积,产生 4000000,另一个int
值。 -
((2000*2000)*2000)
是上面产生的int
值 4000000 与int
值 2000 的乘积。如果该值可以放入int
,这将产生 8000000000。但是我们假设的 32 位 int 可以存储最大值 231-1=2147483647。所以我们在这一点上就溢出了。 - 如果上面没有溢出,就会发生下一次乘法。
- 将生成的
int
乘积赋值给long long
变量(如果不是溢出),这将保留值。
由于我们确实有溢出,该语句具有未定义的行为,因此不能保证第 3 步和第 4 步。
它与第二个或第三个有什么不同?
long long int n = pow(2000,4);
pow(2000,4)
将 2000
和 4
转换为 double
(参见 some docs on pow
),然后函数实现尽最大努力产生一个很好的近似值结果,作为 double
。然后赋值将此 double
值转换为 long long
。
long long int n = 16000000000000;
文字 16000000000000
太大而无法放入 int
,因此它的类型是下一个可以适合该值的有符号类型。它可能是 long
或 long long
,具体取决于平台。有关详细信息,请参阅 Integer literal#The type of the literal。然后赋值将此值转换为 long long
(或者只是写它,如果文字的类型已经是 long long
)。
第一个是使用整数(通常为 32 位)的乘法。它溢出是因为这些整数不能存储 2000^4
。然后将结果强制转换为 long long int
。
第二个调用 pow 函数,该函数将第一个参数强制转换为 double
并返回一个 double
。然后将结果强制转换为 long long int
。在这种情况下没有溢出,因为数学是在 double 值上完成的。
您可能想在 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(),&status);
}
如您所见,类型为 int
。
在 C 中,您可以使用 (courtesy of):
#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 int
和int
之间的最大类型,即unsigned int
。 unsigned int
将下溢到一个大的正数,当分配给或解释为 int
时,这将是预期的负数。无论涉及的值如何,计算结果始终为 unsigned int
。
C
没有后缀的整数文字的最小默认类型是int
,但只有当文字超过这个值时,它的类型才会变成unsigned int
;如果大于它,则给定 long int
的类型,因此 2000 年都是 int
。然而,在文字上执行的 表达式 的类型,使用一元或二元运算符,使用隐式类型层次结构来决定类型,而不是结果的值(与使用长度的文字本身不同)决定类型的文字)。为了解决这个问题,您必须在 2000 年代使用长后缀 ul
来明确指定文字的类型。
同样,十进制文字的默认类型是 double
,但这可以通过 f
后缀更改。前缀不会改变十进制或整数文字的类型。
字符串文字的类型是char []
,虽然它实际上是一个const char []
,并且只是.rodata
中该字符串文字实际表示中第一个字符的地址,并且可以像使用一元符号 &"string"
的任何数组一样获取地址,它与 "string"
具有相同的值(地址),只是类型不同(char (*)[7]
与 {{1 }}; char[7]
即 "string"
不只是(在编译器级别)指向数组的指针,它是数组,而一元&符号仅提取指向数组的指针大批)。 char[]
前缀将其更改为 u
数组,即 char16_t
; unsigned short int
前缀将其更改为 U
数组,这是一个 char32_t
;并且 unsigned int
前缀将其更改为 L
数组,它是一个 wchar_t
。 int
是 u8
并且不带前缀的字符串使用特定于实现的编码,通常与 char
相同,即 UTF-8,ASCII 是其中的一个子集。一个 raw (R
) prefix 仅适用于字符串文字(并且仅适用于 GNU C(u8
以后))可以作为前缀,即 std=gnu99
或 uR
,但这不会影响类型.
字符文字的类型是 u8R
除非以 int
为前缀(u
为 u'a'
)或 unsigned short int
(U
为 { {1}})。 U'a'
和 unsigned int
在用于字符文字时都是 u8
。字符串或字符文字中的转义序列不会影响编码和类型,它只是将要编码的字符实际呈现给编译器的一种方式。
复数文字L
或int
的类型是10i+1
,其中实部和虚部都可以有后缀,比如10j+1
,在这个case 使虚部变长,整体类型为complex int
,并升级了实部和虚部的类型,所以不管你把后缀放在哪里,也不管你把它放在哪里。不匹配将始终使用两个后缀中最大的作为整体类型。
使用显式转换而不是文字后缀总是会导致正确的行为,如果您正确使用它并且知道它截断/扩展的语义差异(符号扩展 10Li+1
;零扩展 {{ 1}} – 这是基于被转换的文字或表达式的类型,而不是被转换为的类型,因此 complex long int
被符号扩展为 signed
) 一个文字到一个表达式那种类型,而不是文字本身就具有那种类型。
C++
同样,最小默认类型是最小文字基数的 unsigned
。文字基础即文字的实际值,后缀根据下表影响最终文字类型,其中在每个后缀的每个框中,最终类型的顺序根据实际的大小从最小到最大列出字面基础。对于每个后缀,文字的最终类型只能等于或大于后缀类型,并基于文字库的大小。 C 表现出相同的行为。当大于 signed int
时,根据编译器,使用 unsigned long int
。我认为您还可以创建自己的文字后缀运算符 int
并返回该类型的值。
十进制文字的默认类型与 C 相同。
字符串文字的类型是 long long int
。 __int128
的类型为 i128
,char []
的类型为 &"string"
(在 C 中,您只能使用 const char (*) [7]
进行衰减)。 C++ 的不同之处在于后两种形式获得 +"string"
,但在 C 中它们没有。字符串前缀的行为与 C
字符和复杂文字的行为与 C 相同。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。