在《算法竞赛进阶指南》提到“在补码下每个数值都有唯一的表示方式,并且任意两个数值做加减法运算,都等价于在32位补码下做最高位不进位的二进制加减法运算。发生算术溢出时,32位无符号整数相当于自动对 232 取模。这也解释了“有符号整数”算术上溢出时出现的负数现象”
但如果直接用位移运算得到 232 时,输出结果却是1
cout<<(1<<32)<<endl; //输出1
感觉有点奇怪,不是溢出后,是对 232 取模吗,按这个说法应该是 0 才对。为什么是 1 呢?
又看到该书在“位移运算”的小节写道:“在二进制表示下把数字同时向左移动,低位以 0 填充,高位越界后舍弃。1<<n=2n , n<<1=2n ”
感觉以左移的方式得到的232 是符合公式 1<<n=2n 的。由于unsigned int 共有32位,1左移32其实是1后面32个0,溢出后,高位抛弃,低位补0,结果应该是 0 ,但这个 0 指的是 2 的 0 次幂,故结果为1.
又注意道第1段话中提到:“发生算术溢出”,应该是指对应的加减乘除等算数运算后的溢出,才相当于对 232 取模,这个不包括位移导致的溢出,位移的溢出又第二段的1<<n=2n 决定。
有试着取到无符号整型的最高值,再加 1 果然结果是 0.
完整代码:
#include <iostream>
using namespace std;
int main(){
unsigned int a=(1ll<<32)-1;
cout<<a<<endl;
cout<<a+1<<endl;
cout<<(a<<32)<<endl;
return 0;
}
为了进一步验证公式 1<<n=2n ,当n大于31的表现,多列出了几个数字进行观察?
#include <iostream>
using namespace std;
int main(){
unsigned int a=1;
cout<<(a<<32)<<endl;
cout<<(a<<33)<<endl;
cout<<(a<<34)<<endl;
cout<<(a<<35)<<endl;
return 0;
}
输出结果为:
PS C:\Users\Jack> ./t
1
2
4
8
通过观察结果,好像是左移的位数进行了模32的运算,比如 1<<32 == 1<<(32%32) == 1<<0 == 20 == 1,而 1<<33 == 1<<(33%32) == 1<<1 == 21 == 2 ,其余的以此类推。
不知道这个技巧是否能做做题中用到,呵呵。
有必要这么细吗?
二进制是计算机运算的基础,尤其是算术运算溢出相当于对232或264取模,这种用法还是会用到的情况下。能细点就细点吧。