通常,高级语言中两个n位整数相乘得到的结果通常也是一个n位整数,即结果只取2n位乘积中的低n位。
–例如,在C语言中,参加运算的两个操作数的类型和结 果的类型必须一致,如果不一致则会先转换为一致的 数据类型再进行计算。
Q: 在计算机内部,一定有 x 2 ≥ 0 x^2≥0 x2≥0 吗?
A:
若x是带符号整数,则不一定!
如x是浮点数,则一定!
例如,当n=4 时, 52=-7<00101 x 0101 —————————— 0101 结果溢出 + 0101 只取低4位,值为-111B=-7 —————————— 00011001
Q: 如何判断返回的z是正确值?
int mul(intx, inty) { int z=x*y; return z; }
A:
①使用程序语言来判断:当 ! x ∣ ∣ z / x = = y ! x || z/x==y !x∣∣z/x==y为真(除数为0/反向验算)
②编译器(使用指令)进行判断:
当 − 2 n − 1 ≤ x ∗ y < 2 n − 1 -2n-1≤ x*y < 2n-1 −2n−1≤x∗y<2n−1(不溢出)时
即:乘积的高n位为全0或全1,并等于低n位的最高位
即:乘积的高n+1位为全0或全1
若x、y和z都改成unsigned类型,则编译器判断方式为:乘积的高n位为全0,则不溢出
假定:
n位无符号整数 | x u x_u xu | y u y_u yu | p u = x u ∗ y u p_u=x_u * y_u pu=xu∗yu |
---|---|---|---|
对应机器数 | X u X_u Xu | Y u Y_u Yu | P u P_u Pu |
n位带符号整数 | x s x_s xs | y s y_s ys | p s = x s ∗ y s p_s=x_s * y_s ps=xs∗ys |
对应机器数 | X s X_s Xs | Y s Y_s Ys | P s P_s Ps |
若 X u = X s Xu=Xs Xu=Xs且 Y u = Y s Yu=Ys Yu=Ys,则 P u = P s Pu=Ps Pu=Ps,但 P u h ≠ P s h Puh≠ Psh Puh̸=Psh(即高位不一定相等)
可用无符号乘来实现带符号乘,但高n位无法得到,故不能判断溢出。
以下程序存在什么漏洞,引起该漏洞的原因是什么。
/* 复制数组到堆中,count为数组元素个数*/
int copy_array(int *array, int count)
{
int i;
/* 在堆区申请一块内存*/
int *myarray = (int *) malloc(count*sizeof(int));
if (myarray == NULL)
return -1;
for (i = 0; i < count; i++)
myarray[i] = array[i];
return count;
/*数溢出,以一段预设信息覆盖一个已分配的堆缓冲区,
造成远程服务器崩溃或者改变内存数据并执行任意代码*/
c o u n t ∗ s i z e o f ( i n t ) count*sizeof(int) count∗sizeof(int) 会溢出。
如 c o u n t = 2 30 + 1 count=2^{30}+1 count=230+1时, c o u n t ∗ s i z e o f ( i n t ) = 4 count*sizeof(int)=4 count∗sizeof(int)=4。不断向内存中复制数据,会使堆(heap)中大量数据被破坏!
整数乘法运算比移位和加法等运算所用时间长,通常一次 乘法运算需要多个时钟周期,而一次移位、加法和减法等运算只要一个或更少的时钟周期,因此,编译器在处理变量与常数相乘时,往往以移位、加法和减法的组合运算来代替乘法运算。
例如,对于表达式 x ∗ 20 x*20 x∗20,编译器可以利用 20 = 16 + 4 = 24 + 22 20=16+4=24+22 20=16+4=24+22,将 x ∗ 20 x*20 x∗20 转换为 ( x < < 4 ) + ( x < < 2 ) (x<<4)+(x<<2) (x<<4)+(x<<2),这样,一次乘法转换成了两次移位和一次加法。
Q: 对于带符号整数来说,n位整数除以n位整数,除 − 2 n − 1 / − 1 = 2 n − 1 -2n-1/-1= 2n-1 −2n−1/−1=2n−1 会发生溢出外,其余情况都不会发生溢出。Why?
A: 因为商的绝对值不可能比被除数的绝对值更大,因而不会发生溢出, 也就不会像整数乘法运算那样发生整数溢出漏洞。
因为整数除法,其商也是整数,所以,在不能整除时需要进行舍入, 通常按照朝0方向舍入,即正数商取比自身小的最接近整数(Floor, 地板),负数商取比自身大的最接近整数(Ceiling,天板)。 例如:7/2=3, -7/2=-3。
整除0的结果无法用一个机器数表示。整数除法时,除数不能为0,否则会发生“异常”,此时,需要调出操作系统中的异常处理程序来处理。
两个代码段
//代码段一:
int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);
//运行结果为-2147483648
//除以-1 被优化成取负指令neg, 故未发生除法溢出
//代码段二:
int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n", c);
//运行结果为“Floating point exception”
//CPU检测到了异常
对于整数除法运算,由于计算机中除法运算比较复杂,而且不能用流水线方式实现,所以一次除法运算大致需要30个或更多个时钟周期,比乘法指令的时间还要长!
例如:
无符号数 14 / 4 = 3 14/4=3 14/4=3: 00001110 > > 2 = 00000011 0000 1110>>2=0000 0011 00001110>>2=00000011
带符号负整数 − 14 / 4 = − 3 -14/4=-3 −14/4=−3
若直接截断,则 11110010 > > 2 = 11111100 = − 4 ≠ − 3 1111 0010 >>2=1111 1100=-4≠-3 11110010>>2=11111100=−4̸=−3
应先纠偏,再右移: k = 2 k=2 k=2, 故 $(-14+22-1)/4=-3 $
即: 11110010 + 00000011 = 1111010111110101 > > 2 = 11111101 = − 3 1111 0010+0000 0011=1111 0101 1111 0101>>2=1111 1101=-3 11110010+00000011=1111010111110101>>2=11111101=−3
Q: 假设 x 为一个int型变量,请给出一个用来计算 x /32的值的函数div32。 要求不能使用除法、乘法、模运算、比较运算、循环语句和条件语句, 可以使用右移、加法以及任何按位运算。
A:
若 x x x为正数,则将 x x x右移 k k k位得到商;
若 x x x为负数,则 x x x需要加一个偏移量 ( 2 k − 1 ) (2k-1) (2k−1)后再右移 k k k位得到商。
因为 32 = 25 32=25 32=25,所以 k = 5 k=5 k=5。 即结果为: ( x > = 0 ? x : ( x + 31 ) ) > > 5 ( x>=0 ? x : (x+31))>>5 (x>=0?x:(x+31))>>5
但题目要求不能用比较和条件语句,因此要找一个计算偏移量 b b b的方式
这里, x x x为正时 b = 0 b=0 b=0, x x x为负时 b = 31 b=31 b=31. 因此,可以从 x x x的符号得到 b b b. x > > 31 x>>31 x>>31 得到的是32位符号,取出最低5位,就是偏移量 b b b。int div32(int x) { /* 根据x的符号得到偏移量b */ int b=(x>>31) & 0x1F; return (x+b)>>5; }
设两个规格化浮点数分别为 A = M a ∗ 2 E a A=Ma *2^{Ea} A=Ma∗2Ea , B = M b ∗ 2 E b B=Mb*2^{Eb} B=Mb∗2Eb ,
A + B = ( M a + M b ∗ 2 − ( E a − E b ) ) ∗ 2 E a A+B=(Ma +Mb*2^{-(Ea-Eb)})* 2^{Ea } A+B=(Ma+Mb∗2−(Ea−Eb))∗2Ea (假设Ea>=Eb)
A − B = ( M a − M b ∗ 2 − ( E a − E b ) ) ∗ 2 E a A-B=(Ma - Mb*2^{-(Ea-Eb)})* 2^{Ea } A−B=(Ma−Mb∗2−(Ea−Eb))∗2Ea (假设Ea>=Eb)
A ∗ B = ( M a ∗ M b ) ∗ 2 E a + E b A*B=(Ma *Mb)*2^{Ea+Eb } A∗B=(Ma∗Mb)∗2Ea+Eb
A / B = ( M a / M b ) ∗ 2 E a − E b A/B=(Ma /Mb)*2^{Ea-Eb} A/B=(Ma/Mb)∗2Ea−Eb
上述运算结果可能出现以下几种情况:
阶码上溢 | 一个正指数超过了最大允许值 | +∞/-∞/溢出 |
---|---|---|
阶码下溢 | 一个负指数超过了最小允许值 | +0/-0 |
尾数溢出 (尾数溢出,结果不一定溢出) |
最高有效位有进位 | 右规 |
非规格化尾数 | 数值部分高位为0 右规或对阶时,右段有效位丢失 |
左规 尾数舍入 |
①无效运算(无意义)
– 运算时有一个数是非有限数,如: 加/ 减∞、0 x ∞、∞/∞等
– 结果无效,如: 源操作数是NaN、0/0、x REM 0、∞REM y 等
②除以0(即:无穷大)
③数太大(阶上溢): 对于SP,指阶码E >1111 1110 (指数大于127)
④数太小(阶下溢): 对于SP,指阶码E < 0000 0001(指数小于-126)
⑤结果不精确(舍入时引起),例如1/3、1/10等不能精确表示成浮点数
上述情况硬件可以捕捉到,因此这些异常可设定让硬件处理,也 可设定让软件处理。让硬件处理时,称为硬件陷阱。
#incldue
#incldue
int mian()
{
int a = 1,b = 0;
printf("Divisionby zero:%d\n",a/b);
getchar();
return 0;
}
//运行结果异常
int main()
{
double x = 1,y = -1.0,z = 0.0;
printf(“Division by zero:%f %f\n",x/z,y/z);
getchar();
return 0;
}
//运行结果:1.#INFOO , -1.#INFOO
//浮点运算中,一个有限数除以0, 结果为正无穷大(负无穷大)
强迫结果为偶数:奇数+1,偶数舍去。最低位为0则舍掉多余位,最低位为1则进位1、使得最低位仍为0(偶数)。
#include
main()
{
float a;
double b;
a = 123456.789e4;
b = 123456.789e4;
printf(“%f/n%f/n”,a,b);
}
/*运行结果如下:
1234567936.000000
1234567890.000000
*/
float可精确表示7个十进制有效数位,后面的数位是舍入后的结果, 舍入后的值可能会更大, 也可能更小.
- C语言中有float和double类型,分别对应IEEE 754单精度浮点数格式和双精度浮点数格式 。
- long double类型的长度和格式随编译器和处理器类型的不同而有所不同,IA-32中是80位扩展精度格式。
int | float | double |
---|---|---|
1S + 31位 | 1S + 8E + (23+1)位 | 1S + 11E + (52+1)位 |
转换 | 结果 |
---|---|
int --> float | 不会发生溢出,但可能有数据被舍入 |
int / float --> double | 因为double的有效位数更多, 故能保留精确值 |
double --> float / int | 可能发生溢出,此外,由于有效位数变少,故可能被舍入 |
float / double --> int | 因为int没有小数部分,所以数据可能会向0方向被截断 |
(数大——溢出,尾数长——舍入)
int x ; float f ; double d ;
//判断是否衡正确
x == (int)(float) x //false 舍入
x == (int)(double) x //true
f == (float)(double) f //true
d == (float) d //flase 溢出/舍入
f == -(-f); //true
2/3 == 2/3.0 //false 整数相除-->整数/浮点数相除-->浮点数
d < 0.0 ((d*2) < 0.0) //true
d > f -f > -d //true
d * d >= 0.0 //true
x*x>=0 //false 高n位舍入,低n位首位可能为1
(d+f)-d == f //false d很大,f很小,f对阶至高位全为0,则左=0,右=f
浮点数不适用加法结合律
x = – 1.5 x 1038 , y = 1.5 x 1038 , z = 1.0 ( x + y ) + z = ( – 1.5 x 1038 + 1.5 x 1038 ) + 1.0 = 1.0 x + ( y + z ) = – 1.5 x 1038 + ( 1.5 x 1038 + 1.0 ) = 0.0 x = –1.5 x 1038, y = 1.5 x 1038, z = 1.0\\ (x+y)+z = (–1.5x1038+1.5x1038 )+1.0 = 1.0 \\ x+(y+z) = –1.5x1038+ (1.5x1038+1.0) = 0.0 x=–1.5x1038,y=1.5x1038,z=1.0(x+y)+z=(–1.5x1038+1.5x1038)+1.0=1.0x+(y+z)=–1.5x1038+(1.5x1038+1.0)=0.0
1996年6月4日,Ariana 5火箭初次航行,在发射仅仅37秒钟后 ,偏离了飞行路线,然后解体爆炸,火箭上载有价值5亿美元的 通信卫星。
原因是在将一个64位浮点数转换为16位带符号整数时,产生了溢 出异常。溢出的值是火箭的水平速率,这比原来的Ariana 4火箭 所能达到的速率高出了5倍。在设计Ariana 4火箭软件时,设计 者确认水平速率决不会超出一个16位的整数,但在设计Ariana 5 时,他们没有重新检查这部分,而是直接使用了原来的设计。
1991年2月25日,海湾战争中,美国在沙特阿拉伯达摩地区设置的爱国者 导弹拦截伊拉克的飞毛腿导弹失败,致使飞毛腿导弹击中了一个美军军营, 杀死了美军28名士兵。其原因是由于爱国者导弹系统时钟内的一个软件错 误造成的,引起这个软件错误的原因是浮点数的精度问题。
从上述结果可以看出:
用32位定点小数表示0.1,比采用float精度高64倍
用float表示在计算速度上更慢,必须先把计数值转换为 IEEE 754格式浮点数,然后再对两个IEEE 754格式的数相乘,故采用float比直接将两个二进制数相乘要慢
不能遇到小数就用浮点数表示,有些情况下(如需要将一个整 数变量乘以一个确定的小数常量),可先用一个确定的定点整 数常量与整数变量相乘,然后再通过移位运算来确定小数点