小言_互联网的博客

int型溢出问题

317人阅读  评论(0)

问题

在执行乘法时( i ! ∗ 2 i , ( i = 0 , 1 , . . . , n − 1 ) i!*2^{i},(i=0,1,...,n-1) i!2i,(i=0,1,...,n1)),当计算的值超过了int上限后却变成了负数。

原理

奇怪的循环

int的范围是:-2147483648~2147483647
如果超出这个范围会出现循环;也就是最大的数+1变成了最小的数,最小的数-1又成了最大的数。所有的数就像模运算一样形成了循环。
如:-2147483648-1=2147483647;2147483647+1=-2147483648
可以看到在INT_MAX后做累加,就回到了INT_MIN。

计算机的编码

对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式。上面的循环就是计算机采用的特殊编码造成的。下面分别做介绍。

1,原码
一开始人们是通过原码来表示整数,整数有正负之分,便通过符号位+二进制的方式表示。(最高位(最左边)取0表示正数,取1表示负数。然后加上对应的二进制。)
+1 = 0000 0001
-1 = 1000 0001

2,反码
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反

3,补码
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补

4,为何选择补码
现代的计算机底层是采用补码来对整数编码。这是因为补码能够极大的简化运算。
对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单.。于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.

使用原码和反码让符号位也参与计算,都会产生错误
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

1 - 1 = 1 + (-1) = [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

而使用补码则不会有这个问题
1-1 = 1 + (-1) = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=0

此外使用补码还能多表示一个负数
0用[0000 0000]表示, 而以前出现问题的-0则不存在了.可以用[1000 0000]表示-128.
这就是为什么现在32位的int范围是-2147483648~2147483647

循环的产生

INT的最大值2147483647的二进制是:
1111111111111111111111111111111
2147483648的二进制是:
10000000000000000000000000000000

换成补码:
0 00000000000000000000000000000000(INTMAX)
0 0111111111111111111111111111111111111(INTMAX+1,溢出了1位)

因为INT只支持32位,就多出来了1位,计算机默认会截除掉最高位(最左边)。
则INTMAX+1的补码就变成了:
0 111111111111111111111111111111111111=-2147483647

计算机巧妙的利用这一点在溢出后达成了一个循环

如何识别溢出

乘法

设int a, b;
ab>INTMAX 显然是无法判断出结果的(溢出了就从负数开始循环)。
换个思路使用b>INTMAX/a
就是把溢出的a
b拆分为未溢出的a和b,然后执行除法来判断是否溢出。
本问题的判断就可以修改如下:

int algo(int n, int a[])
{
   
	a[0] = 0;
	a[1] = 2;
	for (int i = 2; i < n; i++)
	{
   
		a[i] = 2 * i * a[i - 1];
		//if (a[i-1] > INT_MAX / (2*i))
		if(0)
		{
   
			printf("a[i]:%d\n", i);
			printf("堆栈上溢,按出错处理!");
			exit(OVERFLOW);
		}
	}
	return OK;
}

加法

例:检查两个非负整型变量a+b是否溢出
第一种:if(a+b < 0)
这种做法是检查内部寄存器的标志位是否为负

第二种: if((unsigned)a +(unsigned)b >INT_MAX)
这种做法是强制将a和b都强制转换成无符号整数

第三种: if(a > INT_MAX - b)
这种做法不需要用到无符号算术运算符,也和我们之前乘法的处理类似

参考文献

[1]https://blog.csdn.net/weixin_33898233/article/details/92355647.
[2]https://blog.csdn.net/shun_smile/article/details/80042210.
[3]https://blog.csdn.net/zl10086111/article/details/80907428.
[4]https://zhuanlan.zhihu.com/p/340095782.
[5]https://blog.csdn.net/WmxL56/article/details/38380627.


转载:https://blog.csdn.net/xza13155/article/details/115798597
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场