加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

使用位运算代替乘除法

(2012-09-10 12:40:33)
标签:

单片机

位运算

乘除法

it

分类: 单片机

已知 :

unsigned int temp=1000;

   unsigned int result=0;

要求出result = value * 10%

最直接的方法是   :  result = (temp * 10) / 100;

使用位运算的方法是 :  result = (temp >> 4) + (temp >> 5) + (temp >> 8) + (temp >> 9);

MPLAB(PICC-V9.70-Lite Mode)下测试得到的结果显示使用位运算需要的时间仅为乘除法运算的4分之1

先看测试代码、比较一下两种表示方法,再来看如何使用位运算的表示方法。

 

测试代码 :

#include

__CONFIG(0x3f3a);

unsigned int temp=1000;

unsigned int result1=1;

unsigned int result2=1;

int main(void)

{

    result1 = (temp>>4) + (temp>>5) + (temp>>8) + (temp>>9);  //位运算

    asm("nop");

    asm("nop");

    result2 = ( temp *10 )/100;                             //乘除法

    asm("nop");

    asm("nop");

    while(1) ;

}

MPLAB中没有更改晶振、的按默认的20MHz调试的。

使用’ asm("nop");’是为了调试的方便。

结果为 :result1 = 97result2 = 100;

由位运算求出的10%误差是3%、而乘除法的结果没有误差。

在很多情况下都不需要绝对精确的结果、所以上面的差值3是完全可以接受的。

下面来看看使用位运算和乘除法需要的执行时间 :

 

Instruction Cycles

Time(uSecs)

位运算

222

444

乘除法

851

1702

使用位运算需要的时间大概是乘除法运算的4分之1

在反汇编代码中可以看到、乘除法运算过程中调用了乘法和除法的函数(MPLAB自带的) :

乘法 :

http://s11/middle/49cbd2efgc9519c376d6a&690

0x709处定义的乘法 :

http://s2/middle/49cbd2efgc951a02f5c81&690

 

除法 :

http://s4/middle/49cbd2efgc951a1a40283&690

 

0x734处定义的除法:

http://s9/middle/49cbd2efgc951a2e4dda8&690

 

执行乘发和除法需要的时间为 :

 

Instruction Cycles

Time(uSecs)

__wmul

305

610

__lwdiv

505

1010

执行乘法或除法需要的时间都远远比整个位运算多得多。

 

使用这种位运算有几个限制条件 :

1、需要知道系数、比如上面的10%、当然、这个系数是120%、或是8倍都是一样的。

2、结果允许一定的误差、比如上面的97、差值是3。这个误差可大可小、接下来会讲到。

 

现在来看看上面使用的位运算 :

temp10%的表达式

result = (temp >> 4) + (temp >> 5) + (temp >> 8) + (temp >> 9);

是怎么来的呢。

x/64、用移位表示就是x>>6、知道这种表示法、自然就会明白了。

10%用二进制表示出来就是 :

1/(2^4) + 1/(2^5) + 1/(2^8) + 1/(2^9) = 0.099609375、即9.96%、约等于10%

所以结果就是 :

= temp * (  1/(2^4) + 1/(2^5) + 1/(2^8) + 1/(2^9)  )

= temp / (2^4) + temp / (2^5) + temp / (2^8) + temp / (2^9)

= (temp >> 4) + (temp >> 5) + (temp >> 8) + (temp >> 9)

这里注意要加括号’()’、因为移位运算的优先级比加减法低

10%用到减法来表示的情况 :

(1/(2^3) - 1/(2^5) + 1/(2^7) -1/(2^9)) = 0.099609375

计算时使用天下计算器之类的计算软件会很快的得到结果 :

http://s3/middle/49cbd2efgc951a42d7dc2&690

http://s1/middle/49cbd2efgc951a4d49640&690

误差 :

方法很简单、但是要注意误差的存在。

我们的表达式里面有4个部分1/(2^4) + 1/(2^5) + 1/(2^8) + 1/(2^9)

如果只要三项1/(2^4) + 1/(2^5) + 1/(2^8)、结果就是0.09765625=9.76%

误差增大了、要得到越小的误差、需要的项数就越多。

使用5项时误差更小1/(2^4) + 1/(2^5) + 1/(2^8) + 1/(2^9) + 1/(2^11)= 0.10009765625=10.00%

但是使用的项数运算量越大,而且移位的位数越大、运算量也越大。

temp>>9需要移位次、temp>>11需要移位10次。

所以只要误差可接受即可、不必耗费更多的代价来得到更高的精度。

另外,整数运算过程中、如果移位太大、所得结果可能是0,这一点很容易被忽略。

如10>>4就会得到0、移位数量>=4的移位操作都是0,而得不到我们所预想的。

最好在应用之前、先测试具体结果。

空间 :

当然、最好的方法还是将结果做成数组、使用查表取值。

没有运算、不用考虑耗时、也不用担心运算出错。

比如将255分成100 :

const unsigned char table[100]={

2, 5, 7, 10, 12, 15, 17, 20, 22, 25,    //1%~10%

……

.

……                          //90%~100%

};

耗费flash中的100Byte并不算多、很多时候flash都用不完的。

 

 

'1/(2^1)=0.5

'1/(2^2)=0.25

'1/(2^3)=0.125

'1/(2^4)=0.0625

'1/(2^5)=0.03125

'1/(2^6)=0.015625

'1/(2^7)=0.0078125

'1/(2^8)=0.00390625

'1/(2^9)=0.001953125

'1/(2^10)=0.0009765625

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有