这是一个关于数据溢出的问题,如果你对这个知识点感兴趣请继续往下看。引入问题之前我们先温习一下溢出的概念:一个算数运算溢出,是指完整的整数结果不能放到数据类型的字长限制中去。
问题:写出一个具有如下原型的函数,如果参数x 和 y 相加不会产生溢出,这个函数就返回 1.
int tadd_ok(int x,int y);
错误的解决办法:
/* WARNING: This code is buggy. */ boolean tadd_ok(int x,int y){ int sum=x+y; return (sum-x == y) && (sum-y == x); }
这段代码的思路是,如果发生了溢出 (x+y)-y 的结果就不会等于x,且 (x+y)-x 的结果也不会等于y.但实际测试结果是,函数 tadd_ok() 的返回值总是 true。为什么会产生这样的结果呢?下面先来看另一段代码:
public class demo01 { public static void main(String[] args) { int sum=2147483647+3; //IntMax:2147483647 System.out.println("sum="+sum); System.out.println("sum-3="+(sum-3)); } } //output: sum = -2147483646 sum-3 = 2147483647
复制代码
从上面代码中看到,2147483647+3 后发生了溢出,但是溢出后的值减3后又得到了原来的值2147483647。这看起来似乎不合常理,因为发生溢出后,超过数据类型字长限制的部分会被丢弃,这样运算的结果就不会是我们预期的值,而且逆运算之后也更加不会得到原来的初值,但实际情况却不是这样的。
为了更直观地解释这种“诡异”的现象,我们假设机器字长为4位,x=9 和 x=12 的位表示分别为 [1001] 和 [1100],它们的和是21 (5位数字表示为 [10101]),但是机器位只有4位,于是最高位会被丢弃,我们就得到 [0101],也就是十进制的5.我们看到,在 x+y 的运算过程中发生了溢出,导致运算结果5与预期值21不符,但是模拟上面的程序,再进行逆运算会发生什么呢?即运算结果5减去9,如果5-9的结果等于12就说明与上面的程序运行结果相匹配,我们不妨仔细看看运算过程:
5-9 = 5 + (-9) = [0101] + [0111] = [1100] =12.
上面的计算过程可能还不够清晰,’-9’的补码为什么是 [0111],[0111] 不是 7 的补码吗?难道说 -9 =7 吗?在这里给出两种解释方式:(这仅仅是我认为正确的思路,标准答案是什么还望高手不吝赐教)
第一种解释:把 [0111] 并不是 ‘-9’ 的补码,而是表达式 (-9) 的4位二进制表示。我们知道,对于任意整数值 x ,计算表达式 -x 和 ~x+1 得到的结果完全一样。利用这个结论,我们可以得到 ‘-9’ 的位表示推理过程 [1001] 取反得到 [0110], 加1得 [0111].
第二种解释:按照计算机中有符号数的存储规则, ‘-9’ 的补码按照不同位数表示是 [10111] 、[110111]、[1110111]…但是我们已经假设了机器字长为4位,所以必须丢弃超过限定字长的部分,丢弃后得到的补码就是 [0111].这与第一种解释相吻合。
这样我们就能得出一个结论:即使加法产生了溢出,进行逆运算后的值依然等于参与运算的初值,即无论加法是否溢出,而(x+y)-y 总是会求值得到 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; }
后记:数据的溢出往往会造成程序的不可靠性,所以在程序中应该考虑到这个问题所可能产生的后果,并采取必要的措施来处理这种情况。当然,这样做的前提是你已经对溢出有了比较深入的理解。
注:本文整理自《深入理解计算机系统》.
【原文:http://www.cnblogs.com/wd-eco/p/3682009.html】