原题链接1065 A+B and C (64bit)
无论是在pat还是在leetcode中,总是特别喜欢出一些数据取到类型边界的问题。这都涉及到计算机组成原理中的数据在计算机中存放问题。现以PAT甲级1065题为出发点总结如下。
众所周知,数据在计算机中是以二进制补码的形式存放的。其中有符号数的最高位解释为符号位,无符号数的最高位仍未数据位。
以64位的long long为例,一个long long类型的整数在计算机中占据64bit,即8B(字节)。并且最高位即第63位(从0开始)为符号位,取值范围为[-2 ^ 63, 2 ^ 63 - 1],64位补码可以比原码多表示一个数,即-2^63,原码最小只能表示到-2 ^ 63 +1。
记住特殊的几个long long型数据的二进制形式:
十进制下的-1表示为64位的二进制为1…1共64个1
十进制下的2^63 - 1表示为64位的二进制为 01…1共63个1
十进制下的 -2^63表示为64位的二进制为10…0共63个0
当两个long long类型的数据进行相加时,有可能结果超出long long类型的存储范围而发生溢出,包括正溢与下溢。
上溢
a为正数,b为正数,但是a+b超过了long long 的最大的范围 2 ^ 63 - 1,即a+b>2 ^ 63 - 1。
分两种情况
1、和恰好超过了long long 的最大值
a = 1,b = 2 ^ 63 - 1,此时a + b =2 ^ 63 ,是恰好超出了long long的最大取值范围。
a的二进制为0…01B,共63个0,即十六进制的0x0000000000000001。
b的二进制为01…1B,共63个1,即十六进制的0x7FFFFFFFFFFFFFFF。
按照二进制补码相加的原则此时a+b为0x8000000000000000,即为十进制的-2 ^ 63。
2、a = 2 ^ 63 - 1,b= 2 ^ 63 - 1,此时a + b = 2 ^ 64 -2,肯定也是已经超过了long long 的最大正数取值范围了。
a与b的二进制都是01…1B,共63个1,即十六进制的0x7FFFFFFFFFFFFFFF。
此时按照二进制补码相加的原则a + b = 0xFFFFFFFFFFFFFFFE,该数加上1恰好为0xFFFFFFFFFFFFFFFF,即十进制的-1,所以该数为-2。
==故上溢时,有-2 ^ 63 =< a + b < = -2,即a + b 小于0 ==
综上,当上溢时,会有a>0&&b>0&&a+b<0
下溢
a为负数,b为负数,但是a + b 超过了long long 的最小范围 - 2 ^ 63。
同样分为两种情况。
1、a = -1,b = - 2 ^ 63。
a的二进制为1…1B,共64个1,即十六进制的0xFFFFFFFFFFFFFFFF。
b的二进制为10…0B,共63个0,即十六进制的0x8000000000000000。
按照二进制补码加法的原则,此时a + b = 0x7FFFFFFFFFFFFFFF,即为十进制的 2 ^ 63 -1。
2、a = - 2 ^ 63,b = - 2 ^ 63。
此时a 和b的二进制都是10…0B,共63个0,即十六进制的0x8000000000000000。
按照二进制补码相加的原则,a + b = 0x0000000000000000,即为十进制的0。
故下溢时会有 0 < = a + b <= 2 ^ 63 -1
综上,当下溢发生时,有a<0&&b<0&&a+b>=0。
所以原题的代码便为
#include <iostream>
using namespace std;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
long long a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
long long sum = a + b;
if(a > 0 && b > 0 && sum < 0)
printf("Case #%d: true\n",i);
else if(a < 0 && b < 0 && sum >= 0)//注意是 >= 0
printf("Case #%d: false\n",i);
else printf("Case #%d: %s\n",i,sum>c?"true":"false");
}
return 0;
}
这在计算机组成中只是最简单的补码运算,复杂的有乘法和除法,以及浮点数运算,只不过当与数据结构相结合时,边界条件确实是容易使人犯错。