数字(四)
截断
截断其实就是大的数据类型转为小的数据类型时发生的操作。一般截断可以从二进制直观地感受一番,比如int转short时从int中取低16位,在进行相应的补码操作得到short。
无符号数的截断
无符号ω位的位向量x截断为k位的位向量x’,其中ω>k,那么x’ = x % 2k,这很好理解。
有符号数的截断
有符号ω位的位向量x截断为k位的位向量x’,其中ω>k。原理是一样的,只是比无符号数截断多了一些编码操作。如果硬要算的话,x’ = (x + xω-12ω) % 2k - xk-12k。不过知道原理即可,不需要手动计算。
加法运算
无符号数x和y,取值范围都是[0, 2ω-1],它们的和在范围[0, 2ω+1-2]内,它们和的值有一部分超过了取值范围,这就发生了溢出,在计算机中它会被截断,减去2ω。
而有符号数x和y,取值范围为[-2ω-1, 2ω-1-1],和的范围为[-2ω, 2ω-2].同理可得
那么我们如何检验溢出呢?以下以有符号数为例,只有当x<0,y<0并且它们的和sum>=0时发生负溢出,只有当x>0,y>0并且sum<0时发生正溢出。那么接下来就很简单了,方法代码如下:
int tadd_ok(int x, int y)
{
int sum = x + y;
int neg_over = x < 0 && y < 0 && sum >= 0; // 判断是否负溢出
int pos_over = x > 0 && y > 0 && sum < 0; // 判断是否正溢出
return !neg_over && !pos_over; // 1代表没发生溢出
}
既然加法可以判断溢出,那么减法当然也可以。
int tsub_ok(int x, int y)
{
if(y == INT_MIN) return !tadd_ok(x, -y); // INT_MIN在limits.h中
return tadd_ok(x, -y);
}
当y不等于INT_MIN时是没有什么问题的,但是当y = INT_MIN时,-y也等于INT_MIN,如果单纯的直接带入tadd_ok方法是会产生错误结果的,当x>=0时,本应会产生正溢出,但是结果返回1,当x<0时,本应不发生溢出,但是结果返回0。
乘法运算
无符号数的乘法
无符号数x和y的取值范围为[0, 2ω-1],它们乘积的范围为[0, 22ω-2ω+1+1],因此x×y的最终结果是(x×y) % 2ω。
有符号数的乘法
怎么说呢?x×y的最终结果为(x×y) % 2ω-(x×y)ω-12ω
判断是否溢出
首次我们令x×y低ω位的数值和为u(无符号),高ω位的数值和为v(有符号),那么x×y的值等于u+v2ω。其中的u应该等于p+pω-12ω,令t = pω-1 + v,q = p / x,r = p % x,则x×y = p + t2ω,p = q × x + r。只有当t=r=0时,y=q。
int tmult_ok(int x, int y)
{
int p = x * y;
return !x || p / x == y;
}