搜索
bottom↓
回复: 48

谁能帮我设计个产生最小代码量的除法

[复制链接]

出0入0汤圆

发表于 2009-5-1 18:47:10 | 显示全部楼层 |阅读模式
RT

该除法是 K/4.35,如果直接这样,代码量太大了

谁能给个简单算法,K的取值是0-4096,要求12位精度

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入4汤圆

发表于 2009-5-1 19:03:55 | 显示全部楼层
考虑一下用移位做

出0入0汤圆

发表于 2009-5-1 19:18:28 | 显示全部楼层
K/4.35
相当于
K*0.2299
=k*2299/10000

出0入0汤圆

发表于 2009-5-1 21:07:52 | 显示全部楼层
【2楼】 yajira ,把K/4.35变换成 K*2299/10000,相当于要做两次16位整数的乘除法,与直接的 K/4.35 一次浮点运算的效率相差不多。

用移位法,效率应该有不少的提升:

例如  K=(K-(K>>4)-(K>>6)-(K>>10))>>2;    //舍弃小数
或者  K=(K-(K>>4)-(K>>6)-(K>>10)+2)>>2;  //小数四舍五入

运算误差均在 正负1之内,如果觉得精度不够,还可以这样:

      K=((K<<2)-(K>>2)-(K>>4)-(K>>8))>>4;    //舍弃小数
或者  K=((K<<2)-(K>>2)-(K>>4)-(K>>8)+8)>>4;  //小数四舍五入

运算误差均在 正负0.25之内。

出0入0汤圆

 楼主| 发表于 2009-5-1 21:08:16 | 显示全部楼层
yajira 您好

我是不想用除法做

除法产生的代码量真的很大

有没有什么其他办法呢?

出0入0汤圆

 楼主| 发表于 2009-5-1 21:14:43 | 显示全部楼层
【3楼】 cowboy

您好,能把您的思路给我讲一下吗?

是想用移位移出0.2299 来吗?

出0入0汤圆

发表于 2009-5-1 21:28:50 | 显示全部楼层
是的,我3楼的思路就是通过移位得到乘以固定常数0.2299的方法。
K=(K-(K>>4)-(K>>6)-(K>>10))>>2
可以看成
K=(K>>2)-(K>>6)-(K>>8)-(K>>12))
设K为1,则
K = (1>>2)-(1>>6)-(1>>8)-(1>>12)
  = 0.25-0.015625-0.00390625-0.000244140625
  = 0.230224609375
已经和0.2299很接近了。

出0入0汤圆

发表于 2009-5-1 21:42:28 | 显示全部楼层
想知道,式子中移多少位怎么算出来的。 我数学没学好想不出来。

出0入0汤圆

 楼主| 发表于 2009-5-1 21:57:18 | 显示全部楼层
【6楼】 cowboy

谢谢你的帮忙,不过这样移位产生的代码量似乎比直接除还要大啊

搞不明白了

我看了一下反汇编,似乎也调用了个函数,是个移位函数

出0入0汤圆

 楼主| 发表于 2009-5-1 21:58:30 | 显示全部楼层
【7楼】 litchiate 草真多

6楼给算的已经很详细了啊

出0入0汤圆

发表于 2009-5-1 22:06:23 | 显示全部楼层
路过,右移一位是除以二,再移除四,加减组合,四分之三出来了,很老的Z80都是这样算的,它没有乘法器

出0入0汤圆

发表于 2009-5-1 22:12:11 | 显示全部楼层
上面移位数据应该是用工具算出来的吗,牛仔透露一下吧

出0入0汤圆

发表于 2009-5-1 22:15:59 | 显示全部楼层
不知楼主是用哪种MCU,用AVR的代码量可能较少,其它的视乎各自情况可针对性优化。总的来说代码量可能少不了多少,但速度肯定有一定的提高。

出0入0汤圆

 楼主| 发表于 2009-5-1 22:25:44 | 显示全部楼层
【12楼】 cowboy

不管怎么样,还是要谢谢您,让我学到了些知识

还有个问题想请教您

我现在产品出的问题不是代码量的多少,而是我的主程序算了很多东西,同时我开了个UART终端,每次连续接收八个字节

当我主程序中的调用程序太多的时候,UART接收中断接收到得第一个字节就是错误的

当我把主程序中注释掉一部分的时候,UART接收的第一个字节就正确了

搞了2天了还没搞明白是怎么回事呢


您能帮我分析一下吗?

出0入0汤圆

发表于 2009-5-1 22:47:43 | 显示全部楼层
用Keil C51测试了一下,直接浮点除法需用396字节,先乘再移位需用128字节,3楼第一例移位法只用71字节。

【13楼】 lovehebut 蓝色风暴,按你的描述,很难分析哪里出问题,只能粗略估计,是否主程序处理中激活其它中断事件,导致UART中断处理延误?

出0入0汤圆

 楼主| 发表于 2009-5-1 23:10:47 | 显示全部楼层
我开始开了2个TIMER中断,并且在中断中处理了LED显示,事实证明我的UART经常丢数据,也就是本应该接受8个,结果只收到了3 4 个,后来我把程序改了,只开了一个TIMER中断,并且只是在TIMER中断中改变了标志位,然后就能完整收到8个数据了

可是同时就出现了我上面说的问题,以前从没碰到过这样的问题,我主程序中没有激活其他中断事件

出0入0汤圆

发表于 2009-5-2 00:11:10 | 显示全部楼层
【14楼】 cowboy
学习了。

IAR 8051试了下,移位比直接乘法快多了。

//1000*0.2299=229.9


unsigned int test1(unsigned int K)
{
  return ((K<<2)-(K>>2)-(K>>4)-(K>>8)+8)>>4;
}

unsigned int test2(unsigned int K)
{
  return K*2299UL/10000UL;
}



int main()
{
  asm("nop");
  
  x=test1(1000);   //240周期,结果是230

  asm("nop");
  
  y=test2(1000); //2061个周期,结果是229
  
   asm("nop");
     
  while(1);
  
}

出0入0汤圆

发表于 2009-5-2 01:00:47 | 显示全部楼层
我说的不清楚么? 问的和

【11楼】 KuJJ
积分:112
派别:
等级:------
来自:重庆

是一样的问题。

“上面移位数据应该是用工具算出来的吗,牛仔透露一下吧  ”

出0入0汤圆

发表于 2009-5-2 01:23:38 | 显示全部楼层
如果使用的MCU是AVR的MEGA系列,
使用乘法算是最快的,
先將乘數人工算好,
如65536/4.35=15065(捨去小數點後)
假設K=1000
K放在R19(高位),R18(低位)
乘數放在R17(高位),R16(低位)
結果在R7(高位),R6(低位)

        LDI        R16,Low(15065)
        LDI        R17,High(15065)
        LDI        R18,Low(1000)
        LDI        R19,High(1000)

        CLR        R4
        CLR        R5
        CLR        R6
        CLR        R7

        MUL        R16,R18
        ADD        R4,R0
        ADC        R5,R1
        MUL        R16,R19
        ADD        R5,R0
        ADC        R6,R1
        MUL        R17,R18
        ADD        R5,R0
        ADC        R6,R1
        MUL        R17,R19
        ADD        R6,R0
        ADC        R7,R1

這樣只要24個週期,程序也只佔20個WORD.

抱歉!!我只用ASM寫程式,C僅僅只有看得懂的程度,
所以我沒辦法給出C的代碼.

出0入0汤圆

发表于 2009-5-2 08:11:01 | 显示全部楼层
楼上的的确最快了。

——————————————————————————
第一种方法:
级数逼近,移位运算,运算较快。

第二种方法:
直接乘除法,为避免溢出,需要提升整型,运算很慢。

第三种方法:
直接乘除法改进,只做一次乘法,且不用整型提升,运算飞快。
但是,C语言并没有16位*16位=32位的乘法,需要用汇编来实现。
(折中办法是进行整型提升,不用汇编)

出0入0汤圆

发表于 2009-5-2 09:24:59 | 显示全部楼层
*2299/10000明显不合理,应当*15066/65536,直接弃低16位

出0入0汤圆

发表于 2009-5-2 12:26:51 | 显示全部楼层
对于带硬件乘法器的MCU,【18楼】 jim20090418 确为最快捷方法。
51也带硬件乘法器,按【18楼】方法测试结果,占用代码39字节,速度44机器周期

;入口参数:R6R7(大端储存形式)
;出口参数:R6R7(大端储存形式)
;占用资源:ACC,B,R5,R6,R7,PSW

SUB_DIV435:        
        ;-----------------------
        mov     a,r6
        mov     b,#low(15066)
        mul     ab
        mov     r5,b
        ;---------------------
        mov     a,r7
        mov     b,#high(15066)
        mul     ab
        add     a,r5
        mov     r5,a
        clr     a
        addc    a,b
        mov     r7,a
        ;---------------------
        mov     a,r6
        mov     b,#low(15066)
        mul     ab
        add     a,r5
        mov     a,r7
        addc    a,b
        xch     a,r6
        ;---------------------
        mov     b,#high(15066)
        mul     ab
        add     a,r6
        mov     r7,a
        clr     a
        addc    a,b
        mov     r6,a
        ret

【16楼】 void_c 上官先生
按你test1,Keil C51下编译运行才90周期,怎么IAR 8051需240周期,不会和Keil相差这么远吧。


【11楼】【17楼】的疑问,并没有使用工具,按6楼方式用2的负N次方逐步逼近。

出0入0汤圆

发表于 2009-5-2 13:06:31 | 显示全部楼层
k*2299/10000=k*x/8192
x=1883
所以应该是K(16位)乘1883(16位)然后除8192即(32位积)带进位右移13次即可得结果,
我就是这样做的。
16位AD转换2.5V对应65535
65535的AD值要转换成2.5000V的显示结果就得用上述方法

出0入0汤圆

发表于 2009-5-2 13:16:34 | 显示全部楼层
哈哈,其实我想给出移位的。
然后懒了,觉得整数乘除都差不多。

出0入0汤圆

发表于 2009-5-2 15:16:33 | 显示全部楼层
【21楼】 cowboy  
按你test1,Keil C51下编译运行才90周期,怎么IAR 8051需240周期,不会和Keil相差这么远吧。

————————————————————————————————————————————

看来,IAR 8051速度优化的确不怎么样。
比KEIL差老远。

出0入0汤圆

 楼主| 发表于 2009-5-2 15:31:56 | 显示全部楼层
同意楼上的,我用IAR觉得优化也就那么回事~~~ 没有传说中那么厉害

出0入0汤圆

发表于 2009-5-2 15:37:13 | 显示全部楼层
【25楼】 lovehebut 蓝色风暴

IAR 8051确实有些问题,同样是IAR,感觉IAR8051优化比IAR AVR差老远。
另外IAR速度优化相比空间优化逊色。

出0入0汤圆

发表于 2009-5-3 01:13:59 | 显示全部楼层
做一个16位除16的除数可以固定,怎么样用乘法指令实现呢?我现在用的是移位减法。总共是30多行汇编。但要循环16次花时间较多。
如果能用乘法指令能实现除法就好啊。

出0入0汤圆

发表于 2009-5-3 03:51:48 | 显示全部楼层
16/16,除數固定的話可以用我上面提供代碼,
但有一點需要注意的,這種方式會有一定程度的誤差,
改善誤差的方法就是提高計算的位數.

公式:
A = 被除數
B = 除數
S = 結果
K = 位數(也就是最後計算完捨去的位數,最怏的方式K=8 OR 16 ...以8為單位)

公式
S = A * (2^K / B) / 2^K
至於程序還要看你實際除數的值,
如果在可接受的誤差範圍內除數可以控制在8位以內的話,
程序可以比我上面提供的代碼更短,
執行週期也可以更快.

出0入0汤圆

发表于 2009-5-3 15:35:26 | 显示全部楼层
如果除数不固定的话就没办法用乘法指令实现除法了吧。现在新出的单片机大部分都没有乘除法指令,有一部分有乘法指令。16位乘16位可以用8位的乘法指令。要做无误差的除法的话还是得用移位减法,做一个24位除16的要移位24次,花的时间较多

出0入0汤圆

发表于 2009-5-3 16:43:59 | 显示全部楼层
试试以下方法:
K*0xEB4C/0x40000=0.229885*K

出0入0汤圆

发表于 2009-5-3 16:50:16 | 显示全部楼层
2字节乘2字节乘法运算加右移18次

出0入0汤圆

发表于 2009-5-3 19:44:28 | 显示全部楼层
lin135 wrote:
如果除数不固定的话就没办法用乘法指令实现除法了吧。现在新出的单片机大部分都没有乘除法指令,有一部分有乘法指令。16位乘16位可以用8位的乘法指令。要做无误差的除法的话还是得用移位减法,做一个24位除16的要移位24次,花的时间较多


只要被除數或除數有一方是固定的,就可以用我上述的原理用乘法去運算,
至於被除數與除數兩方皆不固定,那就只能使用正規的移位減法來做了.

出0入0汤圆

发表于 2009-5-4 10:37:11 | 显示全部楼层
"K*0xEB4C/0x40000=0.229885*K"

不好意思,搞错了
应为:
K*60236/(4*65536)=0.229782*K   有一点误差
但是
16位乘16位可以用8位的乘法指令,所以代码量很小
/65536 不需要运算,实际只要执行2次右移
所以我还是觉得这可能就是最佳方案了,而且精度也可能够了

出0入0汤圆

发表于 2009-5-4 12:04:12 | 显示全部楼层
可以用Q定标数进行定点运算
K Q0表示(16位)
1/4.35=0.229885,Q15表示,定点数是7532(16位)
结果最大值941.379,Q6表示
计算:
val=K*7532;
结果:val>>6;//右移6位

出0入0汤圆

 楼主| 发表于 2009-5-4 12:14:08 | 显示全部楼层
【34楼】 shenxf

能把您的算法详细说一下,我觉得这个算法很好

出0入0汤圆

发表于 2009-5-4 12:36:29 | 显示全部楼层
计算:
val=K*7532;
结果:val>>6;//右移6位
笔误应为:
计算:
val=K*7532;
结果:val>>9;//右移9位

出0入0汤圆

发表于 2009-5-4 13:52:45 | 显示全部楼层
这种算法在DSP中很常见,可Google一下
如果x是浮点数,那么它的Q定点数(可以是16位,也可是32位的,16位用的比较多):
X=x*2^Q
则Q表示小数位数,或者是小数点所在的位。这样有Q0,,Q15定点数,表示数的范围:
Q0:有符号数-32768~32767,无符号数0~65535
。。
Q15: 有符号数-1~0.999969482421875,无符号数0~1.999969482421875
Q16:只有无符号数0~0.9999847412109375
Q定点数运算原则就是对齐小数点。加减法先对齐小数点再运算,乘除先运算在移位。
实际应用主要的问题是如何确定Q的值:依据浮点数的范围来确定。
本例计算K/4.35
K的Q定点数的Q=0,Qk表示,(1/4.35)定点数的Q=15,Qc表示,这样
m=K/4.35=Qk/2^0*Qc/2^15=(Qk*Qc)/2^(0+15)=(Qk*Qc)/2^15
积m的最大值=941.379,可以用Q=6的定点数Qm表示
m=Qm/2^6=(Qk*Qc)/2^15,因此
Qm=(Qk*Qc)/2^9;//注意(Qk*Qc)结果是32位的)

出0入0汤圆

发表于 2009-5-4 14:41:48 | 显示全部楼层
val=K*15059  其中低位两字节为小数部分

出0入0汤圆

 楼主| 发表于 2009-5-4 16:52:52 | 显示全部楼层
【37楼】 shenxf

受教了~~~

容我先好好理解一下~ 嘿嘿

出0入0汤圆

发表于 2009-5-5 09:23:03 | 显示全部楼层
【37楼】 shenxf 的理论是正确的,

m=K/4.35=Qk/2^0*Qc/2^15=(Qk*Qc)/2^(0+15)=(Qk*Qc)/2^15

但是最后说到的Qm令人费解

不如变换一下:

m=K/4.35=(Qk*Qc)/2^15=2*(Qk*Qc)/2^16=2*K*7532/2^16=K*15064/2^16

K*15064/2^16------"K*15064  其中低位两字节为小数部分"

我在33楼,38楼给出的数字存在计算错误,正确的应为K*15064  ....

出0入0汤圆

发表于 2010-5-2 10:12:20 | 显示全部楼层
学习了

出0入0汤圆

发表于 2010-11-19 23:27:38 | 显示全部楼层
学习了

出0入0汤圆

发表于 2016-12-21 07:54:05 | 显示全部楼层
好帖子,学习,希望后来者补充更详细些就更好了

出0入0汤圆

发表于 2016-12-21 08:04:51 来自手机 | 显示全部楼层
4455/1024=4.35

出0入8汤圆

发表于 2018-3-7 10:47:04 | 显示全部楼层
mark   
               

出0入0汤圆

发表于 2019-12-16 14:06:38 | 显示全部楼层
cowboy 发表于 2009-5-1 21:07
【2楼】 yajira ,把K/4.35变换成 K*2299/10000,相当于要做两次16位整数的乘除法,与直接的 K/4.35 一次浮 ...

mark一下
翻这个帖子翻了好久找不到...

出200入2554汤圆

发表于 2019-12-16 15:50:24 | 显示全部楼层
的确是个不错的帖子,标记学习了

出95入100汤圆

发表于 2019-12-16 16:52:33 | 显示全部楼层
放大用整数就可以了
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 16:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表