除法计算约定
除法运算对应的汇编指令分为有符号idiv和无符号div两种。
所谓对x向下取整,就是取得在-∞方向最接近x的整数值,就是取得不大于x的最大整数 。
a. 向下取整
例如,+3.5向下取整得到3,-3.5向下取整得到-4
b. 向上取整
就是取得不小于x的最小整数
例如,+3.5向上取整得到4;-3.5向上取整得到-3。
c. 向零取整
所谓对x向零取整,就是取得往0方向最接近x的整数值,就是放弃小数部分。
例如,+3.5向零取整得到3,-3.5向零取整得到-3
正数就是向下取整,负数就是向上取整
移位向下取整
除法相关的数学性质
五个性质:
- | r | < | b |
- a = bq + r
- b = (a - r ) / q
- q = (a - r ) / b
- r = a - qb
int main(int argc, char* argv[])
{
/*
a:被除数
b:除数
q:商
r:余数
余数 等于 被除数 减去 除数乘以商
*/
// a / b = q
// r = a - bq
printf("%d\r\n",10%3); // 1
printf("%d\r\n",-10%3); //r = -10 - -3*3 = -1
printf("%d\r\n",10%-3); //r = 10 - -3*-3 = 1
printf("%d\r\n",-10%-3);//r = -10 - -3*3 = -1
return 0;
}
相关定理和推导
定理1:
定理2:
定理3:
定理4:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVAV1EMC-1684413297180)(https://cdn.nlark.com/yuque/0/2023/png/27860795/1684073049212-15f34a8b-829d-457a-a317-dd0b338e160b.png#averageHue=%23fdfdfc&clientId=u0156e573-1819-4&from=paste&height=102&id=uad8d2a57&originHeight=140&originWidth=557&originalType=binary&ratio=1.375&rotation=0&showTitle=false&size=13783&status=done&style=none&taskId=u265bf9b7-34d0-4986-878b-d4e69aab1b5&title=&width=405.09090909090907)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mEZdd8ec-1684413297181)(https://cdn.nlark.com/yuque/0/2023/png/27860795/1684073940558-6daab52d-8cd1-45c8-817e-4a1292c5903e.png#averageHue=%23fefefe&clientId=u0156e573-1819-4&from=paste&height=274&id=u3f0910bd&originHeight=377&originWidth=1224&originalType=binary&ratio=1.375&rotation=0&showTitle=false&size=91404&status=done&style=none&taskId=ud54f5983-39d4-431d-958e-80e89f3040d&title=&width=890.1818181818181)]
一、除法优化概念
- 如果除数是变量,则只能使用除法指令。如果除数为常量,就有了优化的余地。
- 数学中的除法是先除后取整,而C语言中是先取整后除(向0取整)。
- 在C语言中:
- 两个无符号整数相除,结果依然是无符号的;
- 两个有符号整数相除,结果则是有符号的;
- 有无符号混乘 ,结果是有符号,用imul
- 如果有符号数和无符号数混除,其结果则是无符号的
- 有符号数的最高位(符号位)被作为数据位对待,然后作为无符号数参与计算。
二、编译器优化–除数为整型常量的除法
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("%d\r\n",10 / 8); // 常量折叠
printf("%d\r\n",10 /argc); // 除数为变量时无法优化,根据除数类型选择div或idiv
printf("%d\r\n",argc /8); // 除数为常量可以优化
return 0;
}
:::tips
移位是向下取整的
在C语言中是向零取整的,也就是说正数是向下取整 负数是向上取整
那么只有正数符合移位的向下取整,而负数要下整转上整
根据推导7可得
:::
这是有分支的做法
int main(int argc, char* argv[])
{
for(int i = 50;i >= -50;i--)
{
printf("%d / 8 = %d\r\n",i,i/8);
//移位是向下取整的
//在C语言中是向零取整的,也就是说正数是向下取整 负数是向上取整
//那么只有正数符合移位的向下取整,而负数要下整转上整
//根据推导7可得
if(i<0)
{
printf("%d >> 3 = %d\r\n\r\n",i,(i+8-1)>>3);
// printf("%d >> 3 = %d\r\n\r\n",i,i>>3);
}
else
{
printf("%d >> 3 = %d\r\n\r\n",i,i>>3);
}
}
return 0;
}
无分支做法
mov eax, i
cdq ;if i >= 0,then edx = 0,else edx = 0xffffffff
and edx, 7 ;if i >= 0,then edx = 0,else edx = 7
add eax, edx;if i >= 0,eax = eax + 0 = eax,else eax = eax + 7
sar eax, 3
:::tips
cdq : 就是将eax的最高位填充到edx
0000 0000
and:
0000 0111
= 0000 0000
1111 1111
and:
0000 0111
= 0000 0111
:::
除数为有符号数字2的幂
int main( int argc, char* argv[])
{
printf("%d\r\n",argc / 8);
return 0;
}
代码定式
mov eax, A
cdq ;符号位进到edx,A>=0,edx=0; A<0,edx=‐1
and edx, 2的n次方 ‐ 1 ;A>0,edx=0;A<0,edx=7
add eax, edx
sar eax, n ;sar逻辑右移
除数为有符号数字 2
int main( int argc, char* argv[])
{
/*
mov eax,argc
cdq ;if argc >= 0, then edx = 0,else edx = -1
sub eax,edx
sar eax,1
*/
printf("%d\r\n",argc / 2);
return 0;
}
代码定式
mov eax,dword ptr [ebp+8]
cdq //符号位进到edx,A>=0,edx=0; A<0,edx=‐1
sub eax,edx
sar eax,1
push eax
除数为有符号数字-2
int main( int argc, char* argv[])
{
/*
mov eax,argc
cdq ;if argc >= 0, then edx = 0,else edx = -1
sub eax,edx
sar eax,1
*/
printf("%d\r\n",argc / -2);
return 0;
}
除数为无符号数字2的幂
int main( unsigned int argc, char* argv[])
{
printf("%d\r\n",argc / 8);
return 0;
}
直接移位
遇到shr直接移位的就是除以2的幂
除数为有符号数字2的幂的负数
int main(int argc, char* argv[])
{
printf("%d\r\n",argc / -8);
return 0;
}
根据向零取整的性质,可以将负号提到外面去
int main(int argc, char* argv[])
{
printf("%d\r\n",-(argc / 8));
return 0;
}
IDA查看
除数为无符号数字2的幂的负数
直接就是div
int main(unsigned argc, char* argv[])
{
printf("%d\r\n", argc / -2);
return 0;
}
int main(unsigned argc, char* argv[])
{
printf("%d\r\n", argc / -8);
return 0;
}
除数为无符号非2的幂
int main(unsigned int argc, char* argv[])
{
printf("%d\r\n",argc /11);
//mul指令会将结果分别存放在edx和eax寄存器中,其中edx存放高32位,eax存放低32位。
//mul是无符号乘法imul是带符号乘法指令
system("pause");
return 0;
}
VC6Debug版本无优化
VC6 Release版本
解析:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J8ZOiqT9-1684413297186)(https://cdn.nlark.com/yuque/0/2023/png/27860795/1684153514465-0523641b-e840-4081-955c-9fb2b2a73338.png#averageHue=%23fefefe&clientId=ufe87fa38-6aed-4&from=paste&height=365&id=ub0180cfe&originHeight=502&originWidth=1308&originalType=binary&ratio=1.375&rotation=0&showTitle=false&size=106882&status=done&style=none&taskId=u5f2e20b1-10b0-40ff-b672-7e7b2729667&title=&width=951.2727272727273)]
2^n 除以C(常量) 触发常量折叠
n值越大 离正确结果就越近
所以在32位系统 n值起步就是32 ,n>=32
M:MagicNumber
数学原型:
- 除法没有分配律(A/(2+3) ≠ A/2+A/3),除法可以优化为时钟周期更短的乘法和移位运算(图解),即定式:
AM >> n <=> A/C = n ;C = 2^n / M 向上取整 ,使用mul ,满足 AM >> n.
.png&originHeight=487&originWidth=614&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102312&status=done&style=shadow&taskId=u32de421b-20a4-4194-b3c2-d093f37d581&title=#averageHue=#faf9f8&id=uTlb9&originHeight=487&originWidth=614&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 结论:
- 1.被除数 * 常量 >> (移位值N):N从32开始(编译器起步值)C越大N跟着涨
- 2.N值越大,结果越精确。N值越小,结果误差越大。
- 3.根据C值确定N值,C是常量,取值范围 2^(32 - 1)。
- **已知M(MagicNum),N(右移几位),反求C(除数)公式: **
C = 2n / M
(最后结果向上取整)。
代码定式:
mov eax, MagicNum //eax中存储幻数(2的n次方÷c)
mul reg/mem //内存 or 寄存器-存储被除数,乘完之后的结果存储在edx.eax中,这步相当于完成了A*M
shr edx, N //右移N位
随堂练习
第一题:
.text:00401001 mov esi, [esp+4+argc]
.text:00401005 mov eax, 0CCCCCCCDh ;3435973837
.text:0040100A mul esi
.text:0040100C shr edx, 2
.text:0040100F push edx
.text:00401010 push offset aD ; "%d\r\n"
.text:00401015 call printf
第二题:
.text:0040101A mov eax, 20000003h ;536870915
.text:0040101F mul esi
.text:00401021 shr edx, 1Dh ;61
.text:00401024 push edx
.text:00401025 push offset aD ; "%d\r\n"
.text:0040102A call printf
第一题:
第一题,向上取整:5
第二题:
向上取整:4294967273
转16进制
点击DWORD,看10进制23,就是-23
int main(unsigned int argc, char* argv[])
{
printf("%d\r\n",argc /-23);
return 0;
}
除数为有符号非2的幂
int main(int argc, char* argv[])
{
printf("argc:%d\r\n",argc/9);
return 0;
}
Debug:
Release:
2^33/954437177 =8,589,934,592 / 954437177
因为移位是向下取整的 ,如果是负数那么就是向上取整的,我们就要下整转上整
所以移位的结果加上1就行,刚好加上符号位(负数的符号位是1)。
:::tips
mov eax,edx
shr eax,31 ;eax的最高位是0
add edx,eax
:::
:::tips
mov eax,edx
cdq
sub eax,edx
cdq执行时:
如果 eax是正数 那么edx是0,如果是负数那就是edx是 -1
:::
cdq:该指令先把edx的每一位置成eax的最高位
代码定式:
mov eax, MagicNumber
imul A ;A是指定操作数,被除数部分
sar edx, n ;edx 右移n位
//========= 拆成两部分看,上半部分,一眼就能看出是带符号的运算(imul,sar),有公式 当x不为整数的时候【x下整 + 1 = x上整】
mov reg, edx
shr reg, 31 //得到符号位 eax中的值,正数是0、负数是1
add edx, reg //正数加0,其值不变||负数加1,下整转上整
//PS:*****************
MOV EAX,EDX
CDQ
SUB EAX,EDX
//下半部分还可以用这三行代替,等价
随堂练习
0x55555556h = 1,431,655,766
1Fh = 31
2^32 / 1,431,655,766 = 3
int main(int argc,char* argv[])
{
printf("argc: %u", argc / 3);
return 0;
}
除数为无符号7
int main(unsigned int argc, char* argv[])
{
printf("%d\r\n",argc / 7);
return 0;
}
:::tips
.text:00401000 mov ecx, [esp+argc]
.text:00401004 mov eax, 613566757
.text:00401009 mul ecx;
.text:0040100B sub ecx, edx
.text:0040100D shr ecx, 1
.text:0040100F add ecx, edx
.text:00401011 shr ecx, 2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWRjPyJU-1684413298363)(null#card=math&code=M= \frac{2^n}{C} ,n>=32&id=D82XY)]
所以(2^32 +C),edx存不下,会出现进位
例:0x1000 0000 + 0x38e38e39
=> 0x138e38e39
所以微软工作人员是从结论出发往式子前推 直到edx存放的下
:::
VC6 Debug版本:
VC6 Release版本:
结论:乘减移加移,M最高位加1
求法:
124924925h 转 十进制
(2^32+3)/4908534053
数学推导:
 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qK5SLGCR-1684413297192)(https://cdn.nlark.com/yuque/0/2022/png/25515810/1657183092243-ea302317-e19e-47d2-95b8-a9bbc83265b5.png#clientId=ufca8b31f-f344-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=669&id=u40675604&margin=%5Bobject%20Object%5D&name=image.png&originHeight=669&originWidth=542&originalType=binary&ratio=1&rotation=0&showTitle=false&size=72465&status=done&style=shadow&taskId=u7700d769-52d9-43c2-b2ee-1406c2ed7c7&title=&width=542#averageHue=%23fafafa&id=s0I1u&originHeight=669&originWidth=542&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
2n / (232 + M) = C
代码定式:
mov eax MegicNnumber
mul A ; imul * 指定操作数
sub A, edx ; 指定操作数 - edx
shr A, 1 ; shr 指定操作数, 1
add A, edx ; 指定操作数 + edx
shr A, n ; shr 指定操作数, n
- 对于此代码定式:
- (1)MegicNumber高位补1个1;
- (2)移的总位数 + n32;
- (3)确定指数。
- 乘减移加移,Magic高位加1。
- 上例24924925h:高位加1 ‐>124924925h。
- 
- 指数 = 32+2+1 = 35
- 
除数为有符号7
- 对于超出megicNumber4字节表达范围的特殊值,比如7的倍数
int main(int argc)
{
printf("%d\r\n", argc / 7);
return 0;
}
Debug:
Release:
2^34 /2454267027 =
- 如上例所示,除法的情况处理起来很复杂。在代码起始处出现了一个超大数值:0x92492493。这个数值是从哪里来的呢?由于除法指令的周期比乘法指令周期长很多,因此编译器会用周期较短的乘法和其他指令代替除法。数学证明
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AikOabee-1684413297194)(https://cdn.nlark.com/yuque/0/2021/png/1773393/1639662566562-6666fb2c-4878-4a19-8b82-83538b5fcc81.png#clientId=u3131d515-f364-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=dMe6x&margin=%5Bobject%20Object%5D&name=image.png&originHeight=70&originWidth=434&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12427&status=done&style=shadow&taskId=uf7f40c26-43cd-4066-a577-8643950b632&title=&width=691#averageHue=%23f9f9f9&id=fPWbs&originHeight=70&originWidth=434&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
- 在乘法指令中,由于edx存放乘积数据的高4字节,因此直接使用edx就等价于乘积右移了32位,再算上.text:0040100B sar edx,1,那就一共移动了33位。在地址.text:0040100D处,eax得到了edx的值,然后对eax右移了1Fh位,对应10进制也就是右移了31位,然后有个很奇怪的加法。其实这里移位的目的是得到有符号数的符号位,如果结果是正数,add edx, eax就是加0,等于什么都没干;如果结果是负数,由于其后面的代码直接使用edx作为计算结果,需要对除法的商调整加1。

~8086+1 = 7F7A


上图解释:
- ax,是有符号数;8086是无符号数
- 要求结果是个有符号数
除数为无符号数字非2的幂的负数
随堂练习
0x100000000h + 0x0Bh = 0x10000000Bh = 4294967307
0x1Fh = 31 4,294,967,289
2^31+32+1 = 2^64/4294967307 = 4,294,967,286 477,218,589
FFFF FFF6 = -10
int main(int argc,char* argv[])
{
printf("argc: %u", (unsigned)argc / -10);
return 0;
}
除数为有符号数字非2的幂的负数
int main(int argc,char* argv[])
{
printf("argc:%d\r\n", argc / -7);
printf("argc:%d\r\n", argc / -11);
}
2^34/2454267027
printf("argc:%d\r\n", argc / -11);
2^33/780,903,145
推导6 -除法有效性问题
主要解释M的指数怎么算,有什么依据。
用于确定除法误差的范围 ,即 确定 n 的值
由 推导 6 可知: M / C = q…r
0<= M /(2^n *C) < | 1/C| n值32起步,当不满时是 n 值+1 在计算,知道 n 上面不等式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZEo8boF-1684413297197)(https://cdn.nlark.com/yuque/0/2022/png/27242010/1657339102956-1d05927a-c13c-423b-84d7-955b60fea02a.png#averageHue=%23fbfbf2&clientId=uc9f27a04-b2e2-4&from=paste&height=800&id=uffa23c4c&originHeight=750&originWidth=423&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99205&status=done&style=none&taskId=ued5eb8ce-28da-4d3c-904a-b8d50fe063c&title=&width=451.2)]
除法总结
- 调整方法:正数是加,负数是减。
(1)除数为变量
div / idiv ;无优化
(2)除数为常量
- 无符号除法:
- 除数为2的幂
- 公式:
c = 2^n
- 定式:
- 公式:
- 除数为2的幂
mov reg, A
shr reg , n ;直接移位
- 除数为非2的幂
- MagicNumber无进位:
- 公式:
c = 2^(n+32) / MagicNumber
- **定式:**基于此原型有四种变化。
- 公式:
- MagicNumber无进位:
mov eax, MagicNumber
**mul ** A
**shr ** edx, n
2. **MagicNumber有进位:**
1. **公式:**`c = 2^(1+n+32) / (MagicNumber + 2^32)`
2. **定式:**
**mul / sub / shr / add / shr **
mov eax, MagicNumber
mul A
sub A, edx
shr A, 1
add A, edx
shr A, n
- 有符号除法 :
- 除数为正
- 除数为2的幂
- 公式:
c = 2^n
- 定式:
- 公式:
- 除数为2的幂
- 除数为正
cdq / and / add / sar
mov eax, A
cdq
and edx, 2^n ‐ 1
add eax, edx
sar eax, n
2. **除数为非2的幂**
1. **MagicNumber为正数:**
1. **公式:**`c = 2^(n+32) / MagicNumber`
2. **定式:**
mov eax, MagicNumber
imul A
sar edx, n;调整取整方向(序列不唯一)
mov reg, edx ; 或:mov eax, edx
shr reg, 31 ; 或:cdq
add reg, edx ; 或:sub eax, edx
2. **MagicNumber为负数:**
1. **公式:**`c = 2^(n+32) / MagicNumber`
2. **定式:**
mov eax, MagicNumber
imul A
add edx, A
sar edx, nmov reg, edx
shr reg, 31
add edx, reg
- 除数为负
- 除数为2的幂
- 公式:
c = ‐(2^n)
- 定式:
- 公式:
- 除数为2的幂
cdq / and / add / sar / neg
mov eax, A
cdq
and edx, 2^n ‐ 1
add eax, edx
sar eax, n
neg eax
2. **除数为非2的幂**
1. **MagicNumber为正数:**
1. **公式:**`c = ‐(2^(n+32) / (2^32 ‐ MagicNumber))`
2. **定式:**
mov eax, MagicNumber
imul A
sub edx, A
sar edx, nmov reg, edx
shr reg, 31
add edx, reg
2. **MagicNumber为负数:**
1. **公式:**`c = ‐(2^(n+32) / neg(MagicNumber))`
2. **定式:**
mov eax, MagicNumber
imul A
sar edx, nmov reg, edx
shr reg, 31
add reg, edx ; ×add edx, reg