如何判断整型算数运算是否溢出

如何判断整型算术运算是否溢出

一个算术运算溢出,是指完整的整数结果不能放到数据类型的字长限制中去。

溢出的原因是显而易见的,考虑两个非负整数x和y,满足0<=x,y<=(2^w)-1。xy可以被表示为w位无符号数字,然而考虑它们的和的话,我们可以得到0<=x+y<=2^(w+1)-2。这个和需要w+1位来表示。如果我们再用其他数加上这个结果的话,可能我们又将需要w+2,w+3位来表示新的结果。这种持续的“字长膨胀“意味着,如果想要完整地表示算数运算的结果,我们就不能对字长做任何限制(实际上Lisp就是这样干的)。如果我们限制了数据类型的字长,溢出的发生是不可避免的。

由于溢出后的结果往往不是我们想要的结果,我们必须对溢出时的情形另做处理,但在此之前我们必须得先能够判断什么时候发生了溢出。

无符号整数的溢出判断比较简单:对一个字长w的无符号数加法运算,当x+y>=2^w时,第w+1位被程序舍去,相当于在x+y的和的基础上减去了2^w。由于x,y<=(2^w)-1,所以有x+y<x(或者x+y<y)。

int uadd_ok(unsigned x,unsigned y){
    unsigned sum=x+y;
    return sum>=x;
}

有符号整型数加法的溢出判断稍微麻烦一些:我们把可能发生的溢出情况分为正溢出与负溢出,正溢出是指两个非负数相加却得负数的情况,而负溢出是指两个负数相加却得正数的情况。可以证明其他时候都没有发生溢出。

复制代码
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;
}
复制代码

一种常见的错误的判断方法如下:

int tadd_ok(int x,int y){
    int sum=x+y;
    return (sum-x==y)&&(sum-y==x);
}

实际上不论是否溢出,(x+y)-x始终得到y,(x+y)-y始终得到x。至于为什么(x+y)-x始终得到y,讲得学术一点是因为补码加上形成一个阿贝尔群,由阿贝尔群的交换律和结合律有(x+y)-x==y。(其实只要想到从补码加上来理解+-运算,会很自然地理解为什么不能这样判断)。

能够判断整数加法是否溢出后,我们再来看看如何判断如何判断整数减法溢出,一种很自然的做法是:

int tsub_ok(int x,int y){
    return tadd_ok(x,-y);
}

咋一看好像也没有什么不对的,但在写代码时用到负号来取相反数时一定要注意对TMin的处理,因为-TMin=TMin(TMin没有对应的正数),在这个函数中如果不对TMin做处理的话,如果y取TMin那么对任意x<0函数都会判断发生溢出(其实并没有)。

对于TMin的处理很简单,考虑y==TMin的情况下x取何值时x-y会溢出呢,只有x>=0的时候。

改善过后的函数:

复制代码
int tsub_ok(int x,int y){
    if(y==TMin)
        return x>=0?0:1;
    else
        return tadd_ok(x,-y);
}
复制代码

关于TMin的值可由<limit.h>头文件中INT_MIN宏定义获得。

最后我们讨论一下如何判断整数乘法发生溢出,判断溢出的代码如下:

int tmult_ok(int x,int y){
    int p=x*y;
    return !x||p/x==y;
}

该函数溢出判断的正确性的证明可参考CSAPP,此处略去

原文链接:http://www.cnblogs.com/evilkant/p/6028074.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值