CODING - Les multiplications et les divisions entières avec le Z80 par ANTIBUG
 
 
Sommaire:
dernière maj : le 10/05/2007
1 - Préface

Je sais que de nombreux articles ont déjà été rédigés à ce sujet, mais néanmoins je vais y ajouter quelques informations supplémentaires. L’utilisation de la multiplication avec le Z80 n’est pas forcément de tout repos. Comme vous avez pu le constater, les simples multiplications et divisions n’existent pas sur le Z80, mis à part les décalages binaires. Le but de ce cours est de redévelopper ces deux opérations comme elles devraient être intégrées au cœur du microprocesseur. Dans un premier temps nous aborderons le problème de la multiplication avec son aspect théorique et pratique, et dans un second temps la division. Cet article ne parlera pas des méthodes avec des tables pré-calculées. 

 
2 - Nombres signés et compléments de bit

    Il existe trois méthodes pour indiquer qu’un nombre est signé :
 
  • La notation en grandeur exacte 
  • Le complément à 1 
  • Le complément à 2
Pour le Z80, c’est le complément à 2 qui est utilisé mais nous allons étudier ces trois méthodes en détail.
2.1 La notation en grandeur exacte

Cette notation utilise le dernier bit d’un nombre pour indiquer si celui-ci est positif (à 0) ou négatif (à 1), exemple :

Notation exacte de 31 :
 

b7 b6 b5 b4 b3 b2 b1 b0
0 0 1 1 1 1 1 1

Notation exacte de -31 :
 

b7 b6 b5 b4 b3 b2 b1 b0
1 0 1 1 1 1 1 1

La seule différence entre ces deux exemples est le bit 7 qui sert de bit de signe.
 


 
2.2 Le complément de bit à 1

    Pour indiquer qu’un nombre est négatif en notation complément à 1, il suffit d’inverser tous les bits d’un nombre négatif tout en gardant le bit de signe, illustration :

Notation de 24 :
 

b7 b6 b5 b4 b3 b2 b1 b0
O 0 0 1 1 0 0 0

Complément à 1 de -24 :
 

b7 b6 b5 b4 b3 b2 b1 b0
1 0 0 1 1 0 0 0
En notation exacte


b7 b6 b5 b4 b3 b2 b1 b0
1 1 1 0 0 1 1 1
Notation en complément à 1


Important : Le signe n’est pas complémenté ! Le complément à 1 est la notation exact d’un nombre avec inversion de ses bits.
 


 
2.3 Le complément de bit à 2

    La notation en complément à 2 s’obtient en prenant la notation en complément à 1 et en additionnant 1 au nombre ; le bit de signe lui, ne bouge pas, illustration :

Notation de 24 :
 

b7 b6 b5 b4 b3 b2 b1 b0
0 0 0 1 1 0 0 0

Complément à 2 de -24 :
 

b7 b6 b5 b4 b3 b2 b1 b0
1 0 0 1 1 0 0 0
En notation exacte


b7 b6 b5 b4 b3 b2 b1 b0
1 1 1 0 0 1 1 1
Notation en complément à 1


b7 b6 b5 b4 b3 b2 b1 b0
1 1 1 0 1 0 0 0
Notation en complément à 2



    La plupart des microprocesseurs utilise une notation en complément à 2 pour indiquer qu’un nombre est négatif. Les conversions dans les différentes notations ne sont pas compliquées. Pour passer d’une notation complément à 1 en notation exacte, il suffit de complémenter de nouveau chaque bit du nombre (pas le bit de signe), de même, pour passer d’une notation complément à 2 vers une notation exacte il suffit de complémenter de nouveau chaque bit et d’ajouter 1 au nombre obtenu, tout en gardant le bit de signe.

Complément à 1 vers notation exacte :
 

            %10011011 => %11100100 <=> -100
            %00100101 => %00100101 <=> 80 (pas de changement car positif)


Complément à 2 vers notation exacte :
 

            %10110001 => %11001111 <=> -79
            %00011100 => %00011100 <=> 28 (pas de changement car positif)

 
2.4 Multiplication ou division d’un nombre signé

    Sachant que la plupart des microprocesseurs utilisent la notation complément à 2 pour traiter les nombres négatifs, pour multiplier ou diviser un nombre négatif nous transformerons d’abord le nombre négatif en nombre positif, nous effectuerons l’opération de multiplication ou division, puis nous complémenterons de nouveau le nombre obtenu à 2. 

 
3 – Les multiplications
3.1 Les généralités

Pour aborder la multiplication et pour ne pas vous faire peur, sachez que les nombres binaires se multiplient de la même façon que les nombres décimaux. Le calcul de la multiplication est même plus simple en binaire car les chiffres du multiplicateur ne peuvent être que 0 ou 1. Voyons directement comment formaliser cette multiplication non singée:

Comme vous pouvez le constater, ce calcul n’est pas compliqué ! Pour calculer le produit d’une telle opération les microprocesseurs doivent cumuler les produits partiels deux à deux (sauf les microprocesseurs de dernière génération à cache interne). Cette opération étant inexistante dans le Z80 il nous reste plus qu’à réinventer la roue. Voyons maintenant comment nous allons structurer notre programme étape par étape :

Algorithme :

    Il faut savoir que le résultat d’une opération de 2 entiers sur 8 bits est toujours égal à un entier sur 16 bits. Nous allons voir plus tard comment coder une routine de multiplication de deux entiers de 8 bits.
 


 
3.2 Passage en revue des solutions existantes

    La solution miracle est celle qui sera adaptée à votre programme. Nous allons voir ensemble quelques solutions existantes.
 
3.2.1 La multiplication par décalages (2exp n)

    Cette multiplication est la plus simple, celle-ci joue sur le fait qu’un décalage d’un bit vers la gauche équivaut à une multiplication par 2, exemple :
 
                    LD A, 1   ; A = 1 <=> %0001
                    SLA A     ; A = 2 <=> %0010
                    SLA A     ; A = 4 <=> %0100
Comme vous avez pu le remarquer nous avons multiplié A par 2 puis par 4, cette multiplication équivaut avec multiplier A par 2exp n.

 
3.2.2 La multiplication par additions

    Contrairement à la méthode précédente, la multiplication par addition n’est pas limitée à un multiple de 2 ; cette méthode est basée sur l’utilisation d’additions dans les calculs et permet d’utiliser un multiplicateur quelconque. Cependant, celle-ci peut s’avérer relativement longue lorsque vous avez à calculer (1000 * 1000), ex :
 
                    LD HL, 0
                    LD DE, 1000
                    LD B, 1000
                _boucle
                    ADD HL, DE
                    DJNZ _boucle
Je pense que vous imaginez bien que le fait d’effectuer 1000 fois une addition n’est certainement par envisageable ? Vous utiliserez donc cette méthode si vous savez que vous n’avez pas de très grosses opérations à effectuer.

 
3.2.3 La multiplication par retenues

    La multiplication par retenue est basée sur l’utilisation le flag Carry pour détecter un débordement lors des calculs. En effet, nous allons étudier un exemple de multiplication de 2 valeurs sur 8 bits. Rappelez-vous qu’une multiplication binaire n’est qu’une multiplication de 1 et de 0 où chaque nombre 1 rencontré est multiplié par autant de nombres 1 que le multiplicateur en possède, exemple :
 
                    %0001 x %0011 = %0011
                    %0010 x %0011 = %0110
                    %0100 x %0011 = %1100
                    %0101 x %0011 = %1111
                    %0001 x %0010 = %0010
                    %0011 x %0010 = %0110
Je pense que l’exemple est très parlant ! C’est important de comprendre cela car l’exemple que nous allons traiter fonctionne de la même manière. Pour multiplier 2 nombres entiers sur 8 bits, nous allons décaler bit à bit le premier nombre (le multiplicande), pour chaque bit à 1 nous allons le multiplier par les bits du second nombre (le multiplicateur) ; ceci est une sorte de remplacement de bits. Pour détecter si le dernier bit (le plus haut à gauche) du multiplicande est à 1 nous allons utiliser l’indicateur Carry, celui-ci se mettra à 1 lorsqu’un débordement se produira. Passons directement à notre exemple.

Multiplication de 2 nombres entiers de 8 bits chacun (65 x 2), le résultat est contenu dans un entier de 16 bits :


Rappel sur les différents flags du registre F


;***************************************************************
; Exemple de multiplication de 2 entiers non signés, de 8 bits
;
; En entrée :
;       H <=> Multiplicande
;       E <=> Multiplicateur
; En sortie :
;       HL <=> Produit de H par E
; Registres affectés
;       F, HL, DE, B

MATH_MUL_8x8 :

    LD L, 0
    LD D, L
    LD B, 8

_MATH_MUL_8x8_1

    ; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
    ADD HL, HL
    JR  nc, _MATH_MUL_8x8_2

    ; Remplacement du bit de débordement par le multiplicateur
    ADD HL, DE 

_MATH_MUL_8x8_2

    DJNZ _MATH_MUL_8x8_1
    RET


 

Cet exemple ce code var servir pour notre démonstration. Prenez un peu de temps pour étudier ce programme, même si celui-ci est très simple faites quelques tests sur papier. Quelque soit le nombre en entrée, ce programme effectuera quand même une rotation du multiplicande, des 8 bits. Regardons maintenant en détail ce qui se passe :
 

1ère étape :

Pour H = 65, L = 0, D = 0, E = 2, B = 8

2ème étape :

3ème étape :

n ème étape :

7ème étape :

8ème étape :


Au final, tous les bits à 1 du multiplicande ont été remplacés par ceux du multiplicateur, ceci équivaut au produit de 65 x 2 = 130 <=> %10000010.


 
3.2.4 La multiplication par ajout de sous-produits

    La multiplication par sous-produits a été détaillée en 2.1 dans « Les généralités ». Si vous avez bien étudié sont fonctionnement, vous verrez que le nombre d’opérations (d’additions) dépend du nombre de bits du multiplicateur. En effet nous avons autant de sous-additions que de bits à 1 dans le multiplicateur. Cette méthode est celle employée dans les microprocesseurs, les plus modernes optimisent l’addition des sous produits via leur système de cache. Nous allons directement passer à la partie code :
 
 
;***************************************************************
; Exemple de multiplication de 2 entiers non signés, de 8 bits
;
; En entrée
;       A <=> Multiplicateur
;       B <=> Multiplicande
; En sortie
;       HL <=> Produit de A et de B (résultat sur 16 bits)
; Registres affectés
;       F, HL, A, B

MATH_MUL_8x8:

    LD HL,0     ; Mise à blanc de HL pour le retour

    OR A        ; Si le A = 0 on sort
    RET z
    CP B        ; Vérification si A < B
    JP c, _MATH_MUL_8x8_1
    LD L, A     ; Echange du multiplicande avec le
    LD A, B     ; multiplicateur pour traiter moins de bits
    LD B, L     ; possible
    LD L, H
    OR A        ; Si le A = 0 on sort
    RET z

_MATH_MUL_8x8_1

    PUSH DE     ; On préserve DE pour le retour
    LD D, L     ; Le multiplicande (B) est stocké dans
    LD E, B     ; DE pour calculer les sous-produits

_MATH_MUL_8x8_2

    BIT 0, A    ; Vérification si le bit 0 = 0, si oui
                ; on ne cumule pas le sous-produit courant
    JP z, _MATH_MUL_8x8_3
    ADD HL, DE  ; Cumule du sous-produit courant

_MATH_MUL_8x8_3

    EX HL,DE    ; Décalage du sous-produit courant
    ADD HL,HL   ; de 1 bit vers le haut
    EX HL,DE    ;
    SRL A       ; Décalage du multiplicateur de 1 bit
                ; vers le bas
    OR A        ; Vérification que tous les bits du
                ; multiplicateur ont été traités
    JP nz, _MATH_MUL_8x8_2

_MATH_MUL_8x8_END

   POP DE      ; Restauration de DE
    RET 
 

Détails du code Z80 :

    Dans un premier temps HL est initialisé à 0 pour le retour, ensuite A est comparé à 0 de manière à éviter d’effectuer des calculs inutiles. Ensuite A est comparé à B pour voir si le multiplicateur (A) est supérieur au multiplicande (B) ; ci c’est le cas on échange les contenus de A et de B, rappelez-vous que les calculs qui sont effectués dépendent du nombre de bit à 1 dans le multiplicateur (A), plus A est petit moins les calculs sont important. Ensuite nous rentrons dans les calculs proprement dits, DE est initialisé avec B, un premier test est effectué sur le bit 0 de A, si celui-ci est égal à 0 on saute au calcul du sous-produit suivant, sinon on ajoute le sous-produit courant à HL (qui est le produit final), etc. etc. Le calcul du sous-produit correspond à un simple décalage de 1 bit, du multiplicande, vers la gauche, à chaque fois qu’un bit du multiplicateur est analysé.

    Lorsque tous les bits du multiplicateur ont été traités (A = 0) on sort de la fonction, HL contient alors le produit final. L’utilisation d’une telle méthode est intéressante, la consommation en cycles dépend du nombre de bits dans le multiplicateur, elle évite d’exécuter un très grand nombre d’additions, dans une boucle par exemple. Rappelez-vous que le schéma détaillé se trouve au chapitre généralités.
 


 
4 – Les divisions
4.1 Les généralités

Comparé à la multiplication la division reste très simple dans son principe mais un peu plus complexe lors de la phase de codage. Nous verrons d’abord son principe général puis deux exemples concrets de code.


Exemple : Division du nombre 157 (%10011101 en binaire) par 5 (%101)


Le principe est de trouver des sous-dividendes supérieurs ou égaux au diviseur, puis d’effectuer une soustraction entre le sous-dividende courant et le diviseur; effectuer cette opération jusqu’à ce que tous les bits du dividende aient été traités. Voyons comment cela fonctionne étape par étape :

Algorithme :


 
4.2 Passage en revue des solutions existantes

 
4.2.1 La division par décalages (2exp n)

Comme pour la multiplication, la division par décalages est exactement la même méthode utilisée que pour la multiplication, seul le sens du décalage change. La division par décalage équivaut à une division par 2exp n, exemple :
 
                            LD A, 4     ; A = 4 <=> %0100
                            SRL A       ; A = 2 <=> %0010
                            SRL A       ; A = 1 <=> %0001
4.2.2 La division par utilisation de sous-dividendes

Le principe de cette méthode est décrit au 4.1 dans « Les généralités », nous allons voir concrètement comment coder cette méthode. Mettons en pratique l’algorithme décrit plus haut:

Exemple :
 
 

;***************************************************************
; Routine de division de 2 entiers non signés, de 8 bits
;
; En entrée
;       A <=> Dividende
;       B <=> Diviseur
; En sortie
;       A <=> Quotient
;       B <=> Reste de la division entière
; Registres affectés
;       F, A, B
;

MATH_DIV_8x8:

    PUSH DE     ; Préservation de DE
    LD D, A     ; Le dividende et le diviseur sont stockés
    LD E, B     ; respectivement dans D et E
    LD B, 8     ; Les 8 bits du dividende sont traités
    XOR A, A

_MATH_DIV_8x8_1

    SLA D       ; Décalage du bit le plus haut, du dividende dans Carry
    RLA         ; Récupération de Carry dans le sous-dividende
                ; (création du sous dividende)
    CP E        ; Vérification si le sous-dividende est supérieur ou
                ; égal au diviseur. Si oui le quotient = + 1, sinon on
                ; traite le bit suivant, du dividende
    JR c, _MATH_DIV_8x8_2
    INC D       ; Incrémentation du quotient. Sachant que les bits de D
                ; sortent vers le haut (la gauche), les bits du bas vont
                ; servir pour stocker le quotient
    SUB E       ; On soustrait le diviseur du dividende

_MATH_DIV_8x8_2

    DJNZ _MATH_DIV_8x8_1 ; Décrémentation de B de manière à
                         ; à traiter les 8 bits du dividende
    LD B, A     ; Récupération du reste dans B
    LD A, D     ; Récupération du quotient calculé dans A
    POP DE      ; Restauration de DE
    RET
 

Prenez un peu de temps pour étudier ce code, il n’est pas compliqué mais il faut bien comprendre tous les enchaînements. Comme pour la division, c’est à vous d’utiliser telle ou telle méthode, dans certains cas seuls des décalages seront suffisants dans d’autres vous aurez à implémenter une méthode plus complexe.

Nous allons reprendre cet exemple mais avec la division d’un nombre entier 16 bits par un nombre entier de 16 bits :

Exemple :
 
 

;***************************************************************
; Routine de division d’un entier non signé de 16 bits, par un
; entier non signé de 16 bits
;
; En entrée
;       BC <=> Dividende
;       DE <=> Diviseur
; En sortie
;       BC <=> Quotient
;       DE <=> Reste de la division entière (sur 16 bits)
; Registres affectés
;   BC, DE
;

MATH_DIV_16x16:

    PUSH AF     ; Préservation de AF 
    PUSH HL     ; Préservation de HL

    LD HL, 0    ; Servira de sous-dividende
    LD B, A     ; Stockage de la partie haute du diviseur dans B
    LD B, 16    ; Les 16 bits de HL (le dividende) vont être traités
    XOR A, A

_MATH_DIV_16x16_1:

    SLA C       ; Décalage du bit le plus haut de la partie basse du 
                ; dividende, dans Carry
    RLA         ; Récupération de Carry dans la partie haute du dividende
                ; Et décalage de la partie haute du dividende dans Carry
    ADC HL, HL  ; Récupération éventuelle de Carry et création par la
                ; même occasion du sous-dividende
    SBC HL, DE  ; On enlève le diviseur du sous-dividende, si Carry est
                ; allumé c’est que le sous-dividende est inférieur
    JR c, _MATH_DIV_16x16_2
    INC C       ; Incrémentation de C de manière à crée le quotient
                ; (Remplacement des bits du dividende par le quotient)

    DJNZ _MATH_DIV_16x16_1 ; Décrémentation de B de manière à
                ; traiter les 16 bits du dividende

_MATH_DIV_16x16_2:

    ADD HL, DE  ; restauration du sous-dividende car celui-ci est 
                ; inférieur au diviseur
    DJNZ _MATH_DIV_16x16_1  ; Décrémentation de B de manière à
                            ; traiter les 16 bits du dividende

    LD D, H     ; Récupération du reste de la division dans DE
    LD E, L
    LD B, A     ; Partie haute du quotient dans B
                ; La partie basse se trouve déjà dans C
    POP HL      ; Restauration de HL
    POP AF      ; Restauration de AF
    RET
 


 

Si vous avez bien regardé, vous avez remarqué que cette exemple est un peu plus complexe et nécessite d’éclaircir certains points. Dans le code on peut remarquer que le dividende de la division de trouve initialement dans BC puis transféré dans A, pour la partie haute, et C pour la partie basse. En effet, HL va contenir le sous-dividende, le fait de décaler C puis A va permettre de faire sortir les bits par le haut (la gauche de A) puis de les récupérer un à un dans HL via une addition avec retenue.

Pour tester si le sous-dividende (HL) est bien supérieur ou égal au diviseur (DE), l’utilisation de l’opérante CP est impossible, on va donc effectuer une soustraction avec retenue pour contrôler le contenu des deux registres. Si HL est bien supérieur ou égal à DE, on réinjecte un bit à 1 dans C, pour créé le quotient au fur et à mesure, dans le cas contraire on rétablit le sous-dividende et C sera décalé naturellement avec un bit à 0.
 
 

Pour conclure, en ce qui concerne la multiplication et la division, je pense que si vous avez bien étudié toutes les méthodes de calcul. Vous verrez qu’au final ce n’est pas très compliqué ! La dernière partie de cet article rassemble plusieurs fonctions sur la multiplication et la division, fonctions que vous pourrez réutiliser dans vos programmes.
 

Dans le prochain article nous étudierons le rapport entre les opérations, la pile et les registres spécialisés, @bientôt ... 


 
5 – Bibliothèque de méthodes
5.1 Les multiplications par retenues

 
5.1.1 Multiplication de 2 entiers non signés sur 8 bits, résultat sur 16 bits
;****************************************************************
; Routine de multiplication de 2 entiers non signés, de 8 bits
; avec un résultat sur 16 bits
;
; En entrée :
;       A <=> Multiplicande
;       B <=> Multiplicateur
; En sortie :
;       HL <=> Produit de A par B
; Registres affectés
;       F, HL
; Remarques
;       - Si A ou B = 0 => HL = 0 en sortie
;       - A et B sont préservés 
;

MATH_MUL_8x8:

    LD HL, 0    ; Valeur de retour

    ; Vérification des valeurs en entrée
    OR A        ; Test de A
    RET z       ; Si A = 0 on sort
    LD L, A     ; Sauvegarde de A pour le test de B
    LD A, B     ; B est transféré dans A pour le test
    OR A        ; Test de B via A
    LD A, L     ; On restore A

    LD L, H     ; On efface L via H
    RET z   ; Si B = 0 on sort

    ; Début de code lié à la multiplication
    PUSH BC     ; Préservation de BC
    PUSH DE     ; Préservation de DE

    LD D, L     ; D est effacer par L
    LD E, A     ; Le multiplicande va dans E
    LD H, B     ; Le multiplicateur va dans H
    LD B, 8     ; Traitement des 8 bits

_MATH_MUL_8x8_1

    ; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
    ADD HL, HL
    JR nc, _MATH_MUL_8x8_2

    ; Remplacement du bit de débordement par le multiplicateur
    ADD HL, DE  

_MATH_MUL_8x8_2

    DJNZ _MATH_MUL_8x8_1
    POP DE              ; restauration de DE
    POP BC              ; Restauration de BC
    RET
        

 
5.1.2 Multiplication de deux entiers non signés de 16 par 8 bits, résultat sur 16 bits
;***************************************************************
; Routine de multiplication de 2 entiers non signés, de 16 par
; 8 bits, avec un résultat sur 16 bits
;
; En entrée :
;       DE <=> Multiplicande
;       A  <=> Multiplicateur
; En sortie :
;       HL <=> Produit de DE par A (résultat sur 16 bits !)
; Registres affectés
;       F, HL
; Remarques
;       - Si A ou DE = 0 => HL = 0 en sortie
;       - A et DE sont préservés
;

MATH_MUL_16x8:

    LD HL, 0    ; Valeur de retour

    ; Vérification des valeurs d'entrée
    OR A
    RET z
    ADC HL, DE  ; Ajout de DE à HL de manière à vérifier si DE = 0
    RET z       ; Si DE = 0 => z = 1

    ; Début du code lié à la multiplication
    PUSH AC     ; Préservation de AC
    PUSH BC     ; Préservation de BC 
    LD HL, 0    ; Valeur de retour
    LD B, 8     ; Traitement des 8 bits

_MATH_MUL_16x8_1

    ; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
    ADD HL, HL
    ADD A, A
    JR nc, _MATH_MUL_16x8_2

    ; Remplacement du bit de débordement par le multiplicateur
    ADD HL, DE  

_MATH_MUL_16x8_2

    DJNZ _MATH_MUL_16x8_1
    POP BC      ; Restauration de BC
    POP AC      ; Restauration de AC
    RET

 
5.1.3 Multiplication de deux entiers non signés de 16 bits, résultat sur 16 bits
;***************************************************************
; Routine de multiplication de 2 entiers non signés de 16 bits,
; avec un résultat sur 16 bits
;
; En entrée :
;       BC <=> Multiplicande
;       DE <=> Multiplicateur
; En sortie :
;       HL <=> Produit de BC par DE (résultat sur 16 bits !)
; Registres affectés
;       F, HL
; Remarques
;       - Si BC ou DE = 0 => HL = 0 en sortie
;       - BC et DE sont préservés
;

MATH_MUL_16x16 :

    LD HL, 0    ; Valeur de retour

    ; Vérification des valeurs en entrée
    RL L        ; Permet d'effacer Carry
    ADC HL, BC  ; Vérification que BC = 0
    RET z
    LD HL, 0
    ADC HL, DE  ; Vérification que DE = 0
    RET z

    ; Début du code lié à la multiplication
    PUSH AF     ; Préservation de AF
    PUSH BC     ; Préservation de BC
    LD HL, 0    ; Valeur de retour
    LD A, B
    LD B, 16

_MATH_MUL_16x16_1

    ; Multiplication par 2 de HL (décalage d'1 bit vers le haut)
    ADD HL, HL
    SLA C       ; SLA suivi d’un RLA permet de réaffecter les bits de C
    RLA         ; dans A en cas de débordement de C, donc de traiter
                ; les 16 bits du multiplicateur
    JR nc, _MATH_MUL_16x16_2

    ; Remplacement du bit de débordement par le multiplicateur
    ADD HL, DE  

_MATH_MUL_16x16_2

    DJNZ _MATH_MUL_16x16_1
    POP BC      ; Restauration de BC
    POP AF      ; Restaurationde AF
    RET        
       

 
5.2 Les Multiplications par sous-produits

 
5.2.1 Multiplication de deux entiers non signés de 8 bits, résultat sur 16 bits
;***************************************************************
; Routine de multiplication de 2 entiers non signés, de 8 bits
; avec un résultat sur 16 bits
;
; En entrée
;       A <=> Multiplicateur
;       B <=> Multiplicande
; En sortie
;       HL <=> Produit
; Registres affectés
;       F, HL
; Remarques
;       - Si A ou B = 0 => HL = 0 en sortie
;       - A et B sont préservés
;       - Pour optimiser les calculs, vous pouvez vérifier que le
;         multiplicateur est bien inférieur au multiplicande
;

MATH_MUL_8x8:

    LD HL,0     ; Valeur pour le retour

    ; Vérification des valeurs en entrée
    OR A        ; Test de A
    RET z       ; Retour si A = 0
    LD L, A     ; Sauvegarde de A pour le test de B
    LD A, B     ; Transfert de B dans A pour le test
    OR A        ; Test de B via A
    LD A, L     ; On restore A
    LD L, H     ; On efface L via H
    RET z       ; Retour sir B = 0

_MATH_MUL_8x8_1

    ; Début du code lié à la multiplication
    PUSH AF     ; Préservation de AF pour le retour
    PUSH DE     ; On préserve DE pour le retour
    LD D, L     ; Le multiplicande est dans DE pour calculer les sous-produits
    LD E, B     ;

_MATH_MUL_8x8_2

    BIT 0, A    ; Vérification si le bit 0 = 0, si oui
                ; on ne cumule pas le sous-produit courant
    JP Z, _MATH_MUL_8x8_3
    ADD HL, DE  ; Cumule du sous-produit courant

_MATH_MUL_8x8_3

    EX HL,DE    ; Décalage du sous-produit courant
    ADD HL,HL   ; de 1 bit vers le haut
    EX HL,DE    ;
    SRL A       ; Décalage du multiplicateur de 1 bit
                ; vers le bas
    OR A        ; Vérification que tous les bits du
                ; multiplicateur ont été traités
    JP nz, _MATH_MUL_8x8_2

_MATH_MUL_8x8_END

    POP DE      ; Restauration de DE
    POP AF      ; Restauration de AF
    RET       
       

 
5.2.2 Multiplication de deux entiers non signés de 16 par 8 bits, résultat sur 16 bits
;***************************************************************
; Routine de multiplication de 2 entiers non signés, de 16 par
; 8 bits, avec un résultat sur 16 bits
;
; En entrée
;       DE <=> Multiplicande
;       A  <=> Multiplicateur
; En sortie
;       HL <=> Produit de DE x A (résultat sur 16 bits !)
; Registres affectés
;       F, HL
; Remarques
;       - Si De ou A = 0 => HL = 0 en sortie
;       - DE et A sont préservés
;       - Pour optimiser les calculs, vous pouvez vérifier que le
;         multiplicateur est bien inférieur au multiplicande
;

MATH_MUL_16x8

    LD HL,0     ; Mise à blanc de HL pour le retour

    ;Vérification des valeurs en entrée
    OR A        ; Si A = 0 on sort
    RET z
    ADC HL, DE  ; Si DE = 0 on sort
    RET z

    ; Début du code lié à la multiplication
    PUSH AF     ; Préservation de AF pour le retour
    PUSH DE     ; Préservation de DE pour le retour
    LD HL, 0

_MATH_MUL_16x8_1

    BIT 0, A    ; Vérification si le bit 0 = 0, si oui
                ; on ne cumule pas le sous-produit courant
    JP z, _MATH_MUL_16x8_3
    ADD HL, DE  ; Cumule du sous-produit courant

_MATH_MUL_16x8_3

    EX HL,DE    ; Décalage du sous-produit courant
    ADD HL,HL   ; de 1 bit vers le haut
    EX HL,DE    ;
    SRL A       ; Décalage du multiplicateur de 1 bit
                ; vers le bas
    OR A        ; Vérification que tous les bits du
                ; multiplicateur ont été traités
    JP nz, _MATH_MUL_16x8_1

_MATH_MUL_16x8_END

    POP DE      ; Restauration de DE
    POP AF      ; Restauration de AF
    RET        
       

 
5.3 La division par utilisation de sous-dividendes

 
5.3.1 Division de deux entiers non signés de 8 bits, résultat sur 8 bits
;***************************************************************
; Routine de division de 2 entiers non signés de 8 bits, avec un
; résultat sur 8 bits
;
; En entrée
;               A <=> Dividende
;               B <=> Diviseur
; En sortie
;               A <=> Quotient
;               B <=> Reste de la division entière
; Registres affectés
;               F, A , B
; Remarques
;               - A et B sont détruits et remplacé respectivement par
;                 le quotient et le reste de la division 
;

MATH_DIV_8x8:


    PUSH DE     ; Préservation de DE
    LD D, A     ; Le dividende et le diviseur sont stockés
    LD E, B     ; respectivement dans D et E
    LD B, 8     ; Les 8 bits du dividende sont traités
    XOR A, A

_MATH_DIV_8x8_1

    SLA D       ; Décalage du bit le plus haut, du dividende dans Carry
    RLA         ; Récupération de Carry dans le sous-dividende (A)
                ; (création du sous dividende)
    CP E        ; Vérification si le sous-dividende est supérieur ou
                ; égal au diviseur. Si oui le quotient = + 1, sinon on
                ; traite le bit suivant, du dividende
    JR c, _MATH_DIV_8x8_2
    INC D       ; Incrémentation du quotient. Sachant que les bits de D
                ; sortent vers le haut (la gauche), les bits du bas vont
                ; servir pour stocker le quotient
    SUB E       ; On soustrait le diviseur du dividende

_MATH_DIV_8x8_2

    DJNZ _MATH_DIV_8x8_1 ; Décrémentation de B de manière à
                         ; à traiter les 8 bits du dividende
    LD B, A     ; Récupération du reste dans B
    LD A, D     ; Récupération du quotient calculé dans A
    POP DE      ; Restauration de DE
    RET        
        

 
5.3.2 Division de deux entiers non signé de 16 et 8 bits, résultat sur 16 bits
;***************************************************************
; Routine de division d’un entier non signé de 16 bits par un
; entier non signé de 8 bits, avec un résultat sur 16 bits
;
; En entrée
;               BC <=> Dividende
;               A  <=> Diviseur
; En sortie
;               BC <=> Quotient (sur 16 bits)
;               A  <=> Reste de la division entière
; Registres affectés
;               F, BC, A
; Remarques
;               - BC et A sont détruits et remplacé respectivement par
;                 le quotient et le reste de la division 
;

MATH_DIV_16x8:

    PUSH HL     ; Préservation de HL
    LD HL, BC   ; Stockage du dividende dans HL
    LD C, A     ; Stockage du diviseur dans C
    LD B, 16    ; Les 16 bits de HL (le dividende) vont être traités
    XOR A, A

_MATH_DIV_16x8_1

    SLA L       ; Décalage du bit le plus haut, du dividende dans Carry
    RL H
    RLA         ; Récupération de Carry dans le sous-dividende (A)
                ; (création du sous dividende)
    CP C        ; Vérification si le sous-dividende est supérieur ou
                ; égal au diviseur. Si oui le quotient = + 1, sinon on
                ; traite le bit suivant, du dividende
    JR c, _MATH_DIV_16x8_2
    INC HL      ; Incrémentation du quotient (sachant que tous les bits
                ; de HL sont décalé vers le haut (la gauche), les bits
                ; venant du bas vont servir pour stocker le quotient
    SUB C       ; On soustrait le diviseur du dividende

_MATH_DIV_16x8_2

    DJNZ _MATH_DIV_16x8_1 ; Décrémentation de B de manière à
                          ; à traiter les 16 bits du dividende
    LD B, H     ; Récupération du quotient calculé dans HL
    LD C, L     ; Le reste se trouve dans A
    POP HL      ; Restauration de HL
    RET        
        

 
5.3.3 Division de deux entiers non signé de 16 bits, résultat sur 16 bits
;***************************************************************
; Routine de division d’un entier non signé de 16 bits par un
; entier non signé de 16 bits, avec un résultat sur 16 bits
;
;En entrée
;               BC <=> Dividende
;               DE <=> Diviseur
;En sortie
;               BC <=> Quotient
;               DE <=> Reste de la division entière (sur 16 bits)
;Registres affectés
;               BC, DE
; Remarques
;               - BC et DE sont détruits et remplacé respectivement par
;                 le quotient et le reste de la division 
;

MATH_DIV_16x16:

    PUSH AF     ; Préservation de AF    
    PUSH HL     ; Préservation de HL

    LD HL, 0    ; Servira de sous-dividende
    LD A, B     ; Stockage de la partie haute du diviseur dans A
    LD B, 16    ; Les 16 bits de HL (le dividende) vont être traités
    XOR A, A

_MATH_DIV_16x16_1

    SLA C       ; Décalage du bit le plus haut de la partie basse du 
                ; dividende, dans Carry
    RLA         ; Récupération de Carry dans la partie haute du dividende
                ; Et décalage de la partie haute du dividende dans Carry
    ADC HL, HL  ; Récupération éventuelle de Carry et création par la
                ; même occasion du sous-dividende
    SBC HL, DE  ; On enlève le diviseur du sous-dividende, si Carry est
                ; allumé c’est que le sous-dividende est inférieur
    JR c, _MATH_DIV_16x16_2
    INC C       ; Incrémentation de C de manière à crée le quotient
                ; (Remplacement des bits du dividende par le quotient)
    DJNZ _MATH_DIV_16x16_1  ; Décrémentation de B de manière à
                            ; traiter les 16 bits du dividende
    JP _MATH_DIV_16x16_END  ; Saut à la fin lorsque B=0
_MATH_DIV_16x16_2

    ADD HL, DE  ; Restauration du sous-dividende car celui-ci est 
                ; inférieur au diviseur
    DJNZ _MATH_DIV_16x16_1  ; Décrémentation de B de manière à
                            ; traiter les 16 bits du dividende
_MATH_DIV_16x16_END 
    LD D, H     ; Récupération du reste de la division dans DE
    LD E, L
    LD B, A     ; Partie haute du quotient dans B
                ; La partie basse se trouve déjà dans C
    POP HL      ; Restauration de HL
    POP AF      ; Restauration de AF
    RET
        
par –( ANTIBUG )- le 30/03/2007
dernière maj : le 03/04/2007
contact : vincentbouffigny.cpc@free.fr
web : http://ovi.org.free.fr - http://projets.infos.free.fr

 
A voir aussi sur CPCrulez ...

» Coding Z80/Amstrad CPC:Cours et Initiation à l'assembleur  ( Hebdogiciel et Amstar CPC )
» Coding Z80/Amstrad CPC:Informations Technique Divers ...
» A100%: Assembleurs et rubriques bidouilles
» A100%: De l'arcade à l'action par Poum
» A100%: Articles des RUBI Bidouilles
» A100%: Articles des LOGON SYSTEM
» A100%: Les cours de programmtion de l'ASIC / Acces aux nouvelles fonctions du CPC+

» Documentation - MAXAM 464/664/6128
» Documentation - Les vecteurs system
» Documentation - Z80 opcodes reference
» Technique - L'assembleur facile avec votre PC
» Laisser un commentaire ou en discuter sur le forum ...

A voir aussi sur CPCrulez , les sujets suivants peuvent vous intéresser...

» Coding - Crossdev - CCZ80
» Coding - Crossdev - Mycpctoolkit
» Coding - Crossdev - PhrozenC
» Coding - Crossdev - Cpcrslib

» Laisser un commentaire ou  en discuter sur le forum ...