如何在ARM上进行整数(有符号或无符号)除法?

20

我专门从事Cortex-A8和Cortex-A9的工作。我知道有些架构没有整数除法,但除了转换为浮点数、进行除法运算,再将结果转换为整数外,还有什么更好的办法吗?或者这确实是最好的解决方案?

干杯!=)


当然,即使硬件中没有整数除法,编译器也会在软件模式下支持它。我怀疑那些高规格的芯片没有整数除法。我认为ATMega(就像Arduino)缺少它。 - leppie
5
ARM 架构上没有整数除法的汇编指令。 - Phonon
1
要么转换为浮点数,要么使用未卷绕的3个操作码模式进行手动除法。 - Michael Dorgan
1
@Phonon:它不支持SDIVUDIV吗?Cortex-A8是ARMv7,但是根据http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001m/QRC0001_UAL.pdf的说法,只有一些处理器被支持。 - leppie
2
ARMv7-R,ARMv7VE是SDIV和UDIV列出的可选项,否则在ARMv7-A中是没有这些选项的。您必须查看所购买的核心选项和/或查看您正在使用的特定核心的TRM。或者只需编码指令,执行它并查看是否会出现未定义的指令故障... - old_timer
显示剩余2条评论
5个回答

10

用一个常数进行除法可以通过进行64位乘法和右移来快速完成,例如:

LDR     R3, =0xA151C331
UMULL   R3, R2, R1, R3
MOV     R0, R2,LSR#10

这里将R1除以1625。计算方法如下:64位寄存器(R2:R3)= R1*0xA151C331,然后将结果的前32位右移10位:

R1*0xA151C331/2^(32+10) = R1*0.00061538461545751488 = R1/1624.99999980

你可以使用这个公式计算自己的常数:

x / N ==  (x*A)/2^(32+n)   -->       A = 2^(32+n)/N

选择最大的n,使得A < 2^32。


1
这里存在舍入误差。对于无符号32位除以N = 7,我们有n = 2和A = 2454267026.28...。如果我们向下舍入A的值,则会得到一个结果,该结果对于“4294967292 / 7”来说太小了。如果我们向上舍入,则会得到一个结果,该结果对于“4294967291 / 7”来说太大了。只有当A的精确值的小数部分小于0.5时,才会发生这种情况,因此它对于约一半的N值(如3、5或1625)有效。 - Armin Rigo

7

以下是有关整数除法的一些复制粘贴内容: 基本上,每位需要3条指令。此信息来自网站,虽然我也在许多其他地方看到过。 这个网站还有一个很好的版本,可能更快。


@ Entry  r0: numerator (lo) must be signed positive
@        r2: deniminator (den) must be non-zero and signed negative
idiv:
        lo .req r0; hi .req r1; den .req r2
        mov hi, #0 @ hi = 0
        adds lo, lo, lo
        .rept 32 @ repeat 32 times
          adcs hi, den, hi, lsl #1
          subcc hi, hi, den
          adcs lo, lo, lo
        .endr
        mov pc, lr @ return
@ Exit   r0: quotient (lo)
@        r1: remainder (hi)

5
每位表示需要三条指令,但不是每位需要三个时钟周期。每步的所有指令都直接依赖于上一步的标志位设置,这意味着结果延迟取决于核心的3-4个时钟周期。每步可能需要9-12个时钟周期,总共大约需要360个时钟周期。 - John Ripley
听起来不错。如果可以的话,反向乘以定点数总是更好的选择。 - Michael Dorgan

4
编译器通常在其库中包含除法,例如gcclib,我已从gcc中提取它们并直接使用:https://github.com/dwelch67/stm32vld/ 然后是stm32f4d/adventure/gcclib。
将浮点数转换为整数再转回去可能不是最佳解决方案。您可以尝试一下,看看速度如何...这是一个乘法,但同样可以将其变成除法:https://github.com/dwelch67/stm32vld/ 然后是stm32f4d/float01/vectors.s。
我没有计时来查看速度快慢。我正在使用高于cortex-a的cortex-m,两者相差很大,但浮点指令和gcc lib的内容相似,对于cortex-m,我必须构建thumb版本,但您也可以轻松地构建arm版本。实际上,使用gcc应该会自动完成所有工作,您不需要像我在上面的冒险游戏中那样做。其他编译器也是如此,您不需要按照我在上面的冒险游戏中所做的方式进行操作。

2

由于我在网络上找不到无符号版本,因此我编写了自己的无符号除法程序。我需要对64位值进行32位除法以得到32位结果。

内部循环与上面提供的有符号解决方案效率不同,但这确实支持无符号算术运算。如果分子(hi)的高部分小于分母(den),则此例程执行32位除法;否则执行完整的64位除法(hi:lo/den)。结 ​​果在lo中。

  cmp     hi, den                   // if hi < den do 32 bits, else 64 bits
  bpl     do64bits
  REPT    32
    adds    lo, lo, lo              // shift numerator through carry
    adcs    hi, hi, hi
    subscc  work, hi, den           // if carry not set, compare        
    subcs   hi, hi, den             // if carry set, subtract
    addcs   lo, lo, #1              // if carry set, and 1 to quotient
  ENDR

  mov     r0, lo                    // move result into R0
  mov     pc, lr                    // return

do64bits:
  mov     top, #0
  REPT    64
    adds    lo, lo, lo              // shift numerator through carry
    adcs    hi, hi, hi
    adcs    top, top, top
    subscc  work, top, den          // if carry not set, compare        
    subcs   top, top, den           // if carry set, subtract
    addcs   lo, lo, #1              // if carry set, and 1 to quotient
  ENDR
  mov     r0, lo                    // move result into R0
  mov     pc, lr                    // return

可以添加额外的边界条件检查和2的幂次方。完整的详细信息可以在http://www.idwiz.co.za/Tips%20and%20Tricks/Divide.htm找到。


1
我为ARM GNU汇编器编写了以下函数。如果你的CPU不支持机器,请将两个函数中前几行剪切到"0:"标签处。
.arm
.cpu    cortex-a7
.syntax unified

.type   udiv,%function
.globl  udiv
udiv:   tst     r1,r1
        bne     0f
        udiv    r3,r0,r2
        mls     r1,r2,r3,r0
        mov     r0,r3
        bx      lr
0:      cmp     r1,r2
        movhs   r1,r2
        bxhs    lr
        mvn     r3,0
1:      adds    r0,r0
        adcs    r1,r1
        cmpcc   r1,r2
        subcs   r1,r2
        orrcs   r0,1
        lsls    r3,1
        bne     1b
        bx      lr
.size   udiv,.-udiv

.type   sdiv,%function
.globl  sdiv
sdiv:   teq     r1,r0,ASR 31
        bne     0f
        sdiv    r3,r0,r2
        mls     r1,r2,r3,r0
        mov     r0,r3
        bx      lr
0:      mov     r3,2
        adds    r0,r0
        and     r3,r3,r1,LSR 30
        adcs    r1,r1
        orr     r3,r3,r2,LSR 31
        movvs   r1,r2
        ldrvc   pc,[pc,r3,LSL 2]
        bx      lr
        .int    1f
        .int    3f
        .int    5f
        .int    11f
1:      cmp     r1,r2
        movge   r1,r2
        bxge    lr
        mvn     r3,1
2:      adds    r0,r0
        adcs    r1,r1
        cmpvc   r1,r2
        subge   r1,r2
        orrge   r0,1
        lsls    r3,1
        bne     2b
        bx      lr
3:      cmn     r1,r2
        movge   r1,r2
        bxge    lr
        mvn     r3,1
4:      adds    r0,r0
        adcs    r1,r1
        cmnvc   r1,r2
        addge   r1,r2
        orrge   r0,1
        lsls    r3,1
        bne     4b
        rsb     r0,0
        bx      lr
5:      cmn     r1,r2
        blt     6f
        tsteq   r0,r0
        bne     7f
6:      mov     r1,r2
        bx      lr
7:      mvn     r3,1
8:      adds    r0,r0
        adcs    r1,r1
        cmnvc   r1,r2
        blt     9f
        tsteq   r0,r3
        bne     10f
9:      add     r1,r2
        orr     r0,1
10:     lsls    r3,1
        bne     8b
        rsb     r0,0
        bx      lr
11:     cmp     r1,r2
        blt     12f
        tsteq   r0,r0
        bne     13f
12:     mov     r1,r2
        bx      lr
13:     mvn     r3,1
14:     adds    r0,r0
        adcs    r1,r1
        cmpvc   r1,r2
        blt     15f
        tsteq   r0,r3
        bne     16f
15:     sub     r1,r2
        orr     r0,1
16:     lsls    r3,1
        bne     14b
        bx      lr

有两个函数,udiv 用于无符号整数除法,sdiv 用于有符号整数除法。它们都期望在 r1(高位)和 r0(低位)中包含一个64位被除数(有符号或无符号),以及在 r2 中包含一个32位除数。它们将商存放在 r0 中,余数存放在 r1 中,因此你可以在 C 头文件中定义它们为返回64位整数的 extern,然后在之后掩码处理商和余数。当余数的绝对值大于或等于除数的绝对值时,会指示出现错误(除以0或溢出)。有符号除法算法通过区分被除数和除数的符号来实现;它不会先转换为正整数,因为这样无法正确检测所有溢出条件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接