web-dev-qa-db-fra.com

Comment fait-on une division entière (signée ou non signée) sur ARM?

Je travaille en particulier sur Cortex-A8 et Cortex-A9. Je sais que certaines architectures ne comportent pas de division entière, mais quel est le meilleur moyen de le faire, à part convertir en float, diviser, convertir en entier? Ou est-ce bien la meilleure solution?

À votre santé! =)

16
Phonon

Le compilateur inclut normalement une division dans sa bibliothèque, gcclib par exemple, je les ai extraites de gcc et les utilise directement:

https://github.com/dwelch67/stm32vld/ then stm32f4d/adventure/gcclib

flotter et revenir n’est probablement pas la meilleure solution. vous pouvez l'essayer et voir à quelle vitesse il est ... Ceci est une multiplication mais pourrait tout aussi bien en faire une fracture:

https://github.com/dwelch67/stm32vld/ puis stm32f4d/float01/vectors.s

Je n'ai pas eu le temps de voir à quelle vitesse/lent. Compris que j'utilise un cortex-m ci-dessus et que vous parlez d'un cortex-a, de différentes extrémités du spectre, d'instructions float similaires et du contenu de gcc lib est similaire, car le cortex-m doit être construit pour le pouce tout aussi facilement construire pour le bras. En fait, avec gcc, tout devrait fonctionner automatiquement, vous ne devriez pas avoir à le faire comme je l’ai fait. D'autres compilateurs également ne devraient pas avoir besoin de le faire comme je l'ai fait dans le jeu d'aventure ci-dessus.

4
old_timer

La division par une valeur constante se fait rapidement en effectuant une multiplication de 64 bits et un décalage à droite, par exemple, comme suit:

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

ici, R1 est divisé par 1625. Le calcul se fait comme suit: 64bitreg (R2: R3) = R1 * 0xA151C331, le résultat est le décalage supérieur droit de 32bit de 10:

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

Vous pouvez calculer vos propres constantes à partir de cette formule:

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

sélectionnez le plus grand n pour lequel A <2 ^ 32

10
Willem Hengeveld

Quelques copies de pâtes d’ailleurs pour une division entière: Fondamentalement, 3 instructions par bit. De this site web, même si je l’ai déjà vu bien d’autres endroits. This site comporte également une version Nice qui peut être plus rapide en général.


@ 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)
7
Michael Dorgan

J'ai écrit ma propre routine pour effectuer une division non signée car je ne pouvais pas trouver une version non signée sur le Web. Je devais diviser une valeur de 64 bits avec une valeur de 32 bits pour obtenir un résultat de 32 bits. 

La boucle interne n'est pas aussi efficace que la solution signée fournie ci-dessus, mais cela prend en charge l'arithmétique non signée. Cette routine effectue une division de 32 bits si la partie haute du numérateur (hi) est inférieure au dénominateur (den), sinon une division complète de 64 bits est effectuée (hi: lo/den). Le résultat est dans 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

Une vérification supplémentaire des conditions aux limites et de la puissance 2 peut être ajoutée. Vous trouverez tous les détails sur http://www.idwiz.co.za/Tips%20and%20Tricks/Divide.htm

2
selwyn

J'ai écrit les fonctions suivantes pour l'assembleur ARM GNU. Si vous n'avez pas de CPU avec le support machine udiv/sdiv, coupez les premières lignes jusqu'au label "0:" dans l'une ou l'autre fonction.

.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

Il existe deux fonctions, udiv pour la division entière non signée et sdiv pour la division entière signée. Ils s’attendent tous deux à recevoir un dividende 64 bits (signé ou non signé) en r1 (mot élevé) et r0 (mot faible), ainsi qu’un diviseur 32 bits en r2. Ils renvoient le quotient dans r0 et le reste dans r1; vous pouvez donc les définir dans un C header en tant que extern renvoyant un entier de 64 bits et masquant ensuite le quotient et le reste. Une erreur (division par 0 ou dépassement de capacité) est indiquée par un reste ayant une valeur absolue supérieure ou égale à la valeur absolue du diviseur. L'algorithme de division signée utilise la distinction de casse par les signes de dividende et de diviseur; il ne convertit pas d'abord en nombres entiers positifs, car cela ne détecterait pas correctement toutes les conditions de débordement.

0
user371416