Pour que notre Z80 puisse diviser un nombre binaire par un autre, nous allons utiliser la meme méthode que pour la multiplication: la méthode égyptienne ! Si vous vous souvenez bien, le principe de base consistait à dire que multiplier un nombre par un autre revient à le multiplier par chacune des puissances de deux qui composent le multiplicateur, et tout était réglé par quelques décalages et additions. Ainsi, avec la méthode égyptienne, l'opération A*10 se décomposait en A*8+A*2. On serait donc tenté de faire de meme pour la division... Cependant, il y a un hic, car on ne peut pas développer une division n'importe comment: A/10 n'est pas égal à A/8+A/2 !!! Revenons donc un peu en arrière: résoudre une division revient à trouver X égal à A/B (pour ceux qui ne comprendraient pas, continuez à regarder Le Bebete Show...), c'est-à-dire tel que A=B*X. Donc ce n'est pas le diviseur B que l'on va décomposer, mais le dividende A !!! En effet, plusieurs fois de suite, on va multiplier B par des puissances de deux décroissantes (par exemple 16,8,4,etc...) d'exposant C, puis, si on peut soustraire B à A (B<A), on le fait et on met le bit C du résultat à 1, sinon on met juste ce meme bit à 0. Enfin, dans les deux cas, on décrémente C et on boucle jusqu'à C=0. Ca, c'est dans la théorie. Dans la pratique, ça donne ça: 10 ORG &A000 ; Il y a quelques différences avec la 20 LD A,(NUMBER2) méthode théorique: tout d'abord, au lieu 30 OR A ; de multiplier à chaque fois le diviseur 40 RET Z ; par une puissance de deux différente, on 50 LD B,0 ; calcule tout d'abord sa valeur maximale 60 LOOP ADD A,A ; (dans la boucle LOOP), puis on le divise 70 INC B ; par deux (RR C) à chaque parcours de la 80 JR NC,LOOP ; boucle principale DIV_LOOP. 90 RRA ;Par ailleurs, les différents bits du 100 LD C,A ; résultat sont modifiées à l'aide d'une 110 LD A,(NUMBER1) simple rotation (RL E), car les 120 LD E,0 instructions de manipulation de bits du 130 DIV_LOOP CP C ; Z80 (RES et SET) ne désignent les numéros 140 CCF ;de bits que par adressage immédiat et non 150 PUSH AF ; par adressage implicite (phrase 160 RL E ; volontairement incompréhensible pour le 170 POP AF ; commun des programmeurs...). Vous 180 JR NC,NOT ;remarquerez qu'à la fin, A contient le 190 SUB C ; reste de la division entière (aussi 200 NOT RR C ; appelé modulo). 210 DJNZ DIV_LOOP ; Notez également que, si cette routine 220 LD (REST),A ;est prete à l'emploi, elle n'en pas pour 230 LD A,E ; autant optimisée pour des raisons de 240 LD (RESULT),A ;clarte... Notamment, les PUSH et POP AF, 250 RET ;qui servent ici à sauvegarder l'état du 260 NUMBER1 DEFB 0 ; Carry, mais font quand meme, à eux deux, 270 NUMBER2 DEFB 0 ; 7 cycles machine (en clair, 7 280 RESULT DEFB 0 ; microsecondes), auraient pu etre évités 290 REST DEFB 0 ; sans beaucoup de difficultes.D'autre part vous remarquerez qu'il y a deux boucles, dont la première ne sert qu'à préparer le contenu du diviseur (le registre C) et pourrait etre supprimée. C'est ce que nous allons voir avec la routine suivante... 10 DIV LD D,A ; En effet, cette routine est une routine 20 XOR A ; de division 16 par 8 bits avec résultat sur 8 30 LD E,A ; bits également. On profite donc de la phase de 40 SRL D ; conversion du diviseur de 8 à 16 bits pour le 50 RR E ; multiplier par 256, évitant ainsi une première 60 LD C,8 ; boucle de préparation comme dans la routine 70 DIV_LOOP OR A ; précédente. Ensuite on le divise une première 80 SBC HL,DE fois par 2 avant de rentrer dans la boucle 90 JR NC,DIV_YES elle-meme. Pourquoi effectuer cette division 100 ADD HL,DE avant la boucle me direz-vous, puisqu'on la 110 DIV_YES CCF ; retrouve ensuite à l'intérieur de cette 120 RLA ; fameuse boucle DIV_LOOP ? Oui bon c'est que je 130 SRL D ; suis très flemmard et, quand je me suis posé 140 RR E ; la meme question que vous, je n'avais pas 150 DEC C ; vraiment envie de retoucher à ma routine... Si 160 JR NZ,DIV_LOOP vous voulez absolument gagner 4 microsecondes, 170 RET ; vous devrez déplacer les SRL D/RR E au début 180 END ; de la boucle (juste avant le OR A).(Paramètres: HL=dividende, A=diviseur; résultat: A=quotient, HL=reste (modulo)).Maintenant voici le temps pris par ces 2 premières routines : - Division 8 bits : mini. 35 cycles maxi. 210 cycles
- Division 16/8 bits : mini. 155 cycles maxi. 171 cycles
Au niveau rapidité pur, la première routine est donc légèrement plus avantageuse, mais la seconde est beaucoup plus stable et régulière quels que soient les paramètres, ce qui peut etre très utile dans le cas de programmes nécessitant une synchronisation assez précise. Voici maintenant une troisième routine, qui elle vous réalisera une division sur 32 bits purs. Les paramètres sont à transmettre aux adresses suivantes: NUMBER1 pour le dividende, NUMBER2 pour le diviseur. A la fin de la routine, le quotient et le modulo seront contenus respectivement aux labels RESULT et REST. Les 4 octets qui codent chacun de ces entiers seront stockés, comme pour les mots 16 bits, sous le format octet faible-octet fort. 10 ORG &A000 ;:: 350 ;CCF 20 DI ;:: 360 JR C,YES 30 LD HL,(NUMBER2) :: 370 NO ADD HL,DE 40 EXX ; :: 380 EXX 50 PUSH BC ; :: 390 ;ADC HL,DE 60 PUSH DE ; :: 400 EXX 70 PUSH HL ; :: 410 OR A 80 LD HL,(NUMBER2+2) :: 420 YES RL C 90 LD A,H ; :: 430 ;RL B 100 OR L ; :: 440 EXX 110 EXX ; :: 450 ;RL C 120 OR H ; :: 460 ;RL B 130 OR L ; :: 470 ;SRL D 140 JR Z,OVER ; :: 480 ;RR E 150 LD A,1 ; :: 490 EXX 160 PREP INC A ; :: 500 ;RR D 170 ADD HL,HL ;:: 510 ;RR E 180 EXX ; :: 520 DEC A 190 ADC HL,HL ;:: 530 JR NZ,LOOP 200 EXX ; :: 540 LD (RESULT),BC 210 JP P,PREP ; :: 550 LD (REST),HL 220 EXX ; :: 560 EXX 230 EX DE,HL ;:: 570 LD (RESULT+2),BC 240 LD HL,(NUMBER1+2) :: 580 LD (REST+2),HL 250 LD BC,0 ; :: 590 OVER POP HL 260 EXX ; :: 600 POP DE 270 EX DE,HL ;:: 610 POP BC 280 LD HL,(NUMBER1) :: 620 EXX 290 LD BC,0 ; :: 630 EI 300 LOOP OR A ; :: 640 RET 310 SBC HL,DE ;:: 650 NUMBER1 DEFS 4,0 320 EXX ; :: 660 NUMBER2 DEFS 4,0 330 SBC HL,DE ;:: 670 RESULT DEFS 4,0 340 EXX ; :: 680 REST DEFS 4,0Je vous conseille fortement de décortiquer cette routine, ça sera surement très instructif. Si vous vous demandez pourquoi j'utilise les registres alternatifs - qui, rappelons-le, obligent à sauvegarder les anciennes valeurs et à inhiber les interruptions -, sachez que cette routine est à peu près trois fois plus rapide qu'une routine équivalente qui utiliserait les registres 16 bits comme pointeurs sur des cases mémoires. N'hésitez jamais à utiliser les registres alternatifs si vous n'avez pas assez d'un seul jeu de registres et que vous avez des contraintes de rapidité à respecter.C'est enfin terminé pour cette fois-ci ! De toute façon c'en est fini de l'arithmétique qui commencait à devenir très très chiante. La prochaine fois je vous ferai peut-etre un cours sur l'optimisation et tout ça... En attendant vous pouvez toujours m'écrire si vous avez des problèmes de programmation (assembleur mais aussi Basic), des questions sur le hard ou tous autres genres de trucs. Bye... Antoine / POW pour MM&PF |