在算术运算中,带符号整数发生溢出。这是一个未定义行为,什么都可能发生。
下列试图计算 的程序在 较大时触发带符号整数溢出的未定义行为,这可能导致程序崩溃或输出错误结果。
int main()
{
cin >> n;
int ans = 1;
for (int i = 1; i <= n; i++)
ans = ans * i;
cout << ans % 1000000007 << '\n';
}
下列程序试图计算 (保证 ),尽管编写者知道带符号整数溢出的存在,但还是写错了。
int mul_mod(int a, int b)
{
long long r = a * b;
return r % 1000000007;
}
下列程序试图判断 是否会溢出 (保证 ),如溢出则以 INT_MAX
作为结果。然而,由于带符号整数溢出是未定义行为,这个判断将会 (在编译器的优化后) 失败。
int add_clamp(int a, int b)
{
int r = a + b;
return r < 0 ? INT_MAX : r;
}
下列程序试图计算 (保证 ,且 和 在 int
的表示范围以内)。在 恰好是 INT_MAX
时,i++
触发带符号溢出,可能导致程序不终止。
int calc(int l, int r)
{
int ans = 0;
for (int i = l; i <= r; i++)
ans += i / 128;
return ans;
}
下列程序试图计算 (保证 )。无论其原理如何,由于它对于一些输入 (例如,) 会触发带符号溢出,使用它可能导致程序崩溃或输出错误结果。
const long long mod = 1145141919810;
long long mul_mod(long long x, long long y)
{
return (x*y-(long long)((long double)x/mod*y)*mod+mod)%mod;
}
在编译时开启 -fsanitize=undefined -g
即可使得你的程序在触发带符号溢出时,输出错误消息并显示对应的代码行号。但是你仍然需要先构造一组测试用例,使得计算时使用的值大到可以产生溢出。
首先,不建议到处使用 long long
或者 #define int long long
。这会增大程序的工作集,降低缓存命中率,在某些情况下可能导致超时。另外这会造成你的思维惰性,当某题甚至会导致 long long
溢出时你根本就想不到发生甚么事了。
最简单的修复方法是把 i
定义为 long long
,并在运算过程中取模。
int ans = 1;
for (long long i = 1; i <= n; i++)
ans = ans * i % 1000000007;
cout << ans << '\n';
需要进行类型转换,可以写 (long long) a * b
或者 1ll * a * b
。
需要在溢出前判断是否会溢出。
int add_clamp(int a, int b)
{
if (INT_MAX - b < a)
return INT_MAX;
return a + b;
}
最简单的办法是将 定义为 long long
。
改用 unsigned long long
进行计算 (因为无符号整数的溢出严格定义为补码算术)。
const long long mod = 1145141919810;
long long mul_mod(unsigned long long x, unsigned long long y)
{
return (x*y-(unsigned long long)((long double)x/mod*y)*mod+mod)%mod;
}
但是此时要注意不能向该函数传递负数。也可以改用 __int128
(如果评测系统支持的话)。
const long long mod = 1145141919810;
long long mul_mod(long long x, long long y)
{
return (__int128) x * y % M;
}