Pour effectuer des calculs en virgule flottante en utilisant
les instructions assembleur, le programme ci après va consister à générer des
nombres pseudo aléatoires et à calculer la moyenne, l’écart type et le khi afin
de vérifier la validité du générateur.
Je dois vous avouer que ce générateur est très simple et
donc que les nombres générées ne sont pas très aléatoires (voir la théorie sur
la génération de ces nombres et les livres de Knuth).
Le programme vous semble aussi très long mais j’ai voulu
afficher les résultats en écrivant une routine d’affichage des nombres en virgule
flottante simple précision et c’est assez compliqué (et certainement cette
routine peut être améliorée).
ATTENTION remarque du 10/09/2020 : la routine d'affichage est fausse pour de grands exposants (certainement > 32). Une nouvelle routine pour les nombres en double précision est en cours de test et sera publiée dans le chapitre 90. Le chapitre 88 donne une routine qui semble plus exacte à ce jour pour l'assembleur 64 bits.
Lors de la première compilation de ce programme, le
compilateur signale que les instructions en virgule flottante ne sont pas autorisées
sur le processeur de mon Raspberry. Alors que la documentation précise bien que
c’est possible et donc après recherche, j’ai découvert qu’il fallait ajouter la
directive suivante
-mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard
dans le script d’assemblage. Donc j’ai écrit un nouveau
script de compilation et j’ai aussi modifié le link pour qu’il s’exécute par
gcc car le programme fait aussi appel à la fonction printf du C.
Après les déclarations habituelles des messages et des zones
nécessaires, nous initialisons les registres nécessaires au calcul. En virgule
flottante simple précision, nous trouvons de nouveaux registres s0 à s31 et en
double précision les registres d0 à d15 ( mais attentions les registres d
recouvrent les registres s. Voir le chapitre 13 sur http://thinkingeek.com/arm-assembler-raspberry-pi/).
Nous allons effectuer les calculs en simple précision et donc nous utiliserons
les registres s. Pour alimenter s3, il suffit de copier le registre r0 par
l’instruction vmov s3,r0 puis il faut
effectuer une conversion par l’instruction vcvt.f32.s32 s3, s3 (f32 et s32 indique que nous travaillons en
simple précision) car les nombres ont une structure très particulière :
norme IEEE754.
En simple précision, les nombres font 32 bits : le bit
à gauche donne le signe puis les 8 bits suivants représentent l’exposant et les
23 derniers bits la mantisse (pour plus de précisions voir la norme IEEE754)
Après avoir initialisé les 3 registres s2, s3 et s4 (surtout
de jamais oublier d’effectuer la conversion). Nous préparons une table qui va
compter le nombre de tirage pour un chiffre aléatoire tiré, table nécessaire
pour le calcul du KHI.
Puis nous avons une boucle qui va effectuer MAXI tirages en
appelant la fonction de génération. Nous passons à cette fonction la valeur
PLAGE et nous aurons en retour dans r0 un nombre pseudo aléatoire compris entre
0 et PLAGE. La fonction de génération est simple puisque nous nous contentons de
multiplier une graine de départ par un nombre donné et d’y ajouter un autre nombre
pour calculer une nouvelle graine. Ensuite nous effectuons une division par la
plage demandée et nous retournons le reste comme nombre aléatoire.
Nous copions ce nombre
dans le registre s1, et effectuons sa conversion pour le diviser par le nombre
de tirage et l’ajouter dans le registre de totalisation de la moyenne avec les
instructions de calcul en virgule flottante (vdiv, fmul, fadd ). Nous calculons
aussi l’écart type en virgule flottante. Et pour chaque tirage, nous l’ajoutons
aussi dans la table des tirages.
En fin de boucle, nous calculons la moyenne en nombre entier
(en fait simplement pour voir la différence avec le calcul en virgule
flottante) et nous l’affichons par notre fonction d’affichage, nous terminons
le calcul de l’écart type (élévation au carré et calcul de la racine carrée) et
nous l’affichons avec la fonction printf du C. Mais cette fonction n’admet que
les nombres en double précision passés dans les registres r2 et r3. Il nous
faut donc convertir notre résultat par l’instruction vcvt.f64.f32 d5, s3
(noter le f64 et l’utilisation du registre d5) puis alimenter les
registres par vmov r2,r3,d5 pour appeler la fonction du C printf. Cette
fonction utilise le format %f pour éditer correctement le nombre indiqué dans
la chaine szFormat.
Ensuite nous appelons la routine conversionFloatSP pour
convertit le contenu du registre r0 en chaine affichable. J’ai écrit cette
routine pour ne pas faire appel à des fonctions du C. Comme je ne suis pas encore
un expert en assembleur, cette routine n’est certainement pas l’idéal et il
reste des points à améliorer (en particulier les zéros inutiles après la
virgule. Elle est aussi compliquée car pour calculer le résultat qui dépasse la
taille d’un registre, je suis passé par des zones en mémoire pour effectuer
les multiplications et divisions nécessaires. Sur Internet, je n’ai pas trouvé
d’exemples de routines en assembleur qui effectuent cette conversion et j’ai dû
écrire la mienne à partir de la documentation du format IEEE754.
La routine commence par extraire l’exposant et la mantisse du
registre r0 et en fonction de l’exposant va effectuer 4 traitements différents : l’exposant est
égal à 255 , l’exposant est égal à 0, l’exposant est supérieur à 127 et l’exposant
est inférieur à 127. Pour la suite de la routine, je vous suggère d’aller voir
directement le code et les commentaires.
Revenons dans le programme principal pour calculer le
coefficient khi qui permet de juger de
la pertinence du générateur aléatoire. Nous repartons de la table dans laquelle
nous avons enregistré les comptages de chaque tirage pour calculer le total des carrés de chaque compteur.
Ensuite nous effectuons le reste des calculs en virgule flottante pour afficher
le résultat avec la fonction printf du C et avec la routine en assembleur.
Voici le résultat sur mon raspberry :
Comme vous le voyez, la moyenne, l’écart type et le khi sont
proches de ceux attendus (50 , 25 et 100).
Pour les calculs en virgule flottante, les instructions à
utiliser ne sont pas très complexes, il faut surtout veiller à bien mettre les
bonnes extensions .f32.s32 et pensez à
effectuer les conversions entier vers float.
Et dans ce programme, je n’ai pas fait appel à des données
en virgule flottante. Si c’est nécessaire il faut les coder comme ceci :
pi: .float 0E-3141592E-6
et l’utiliser par ldr r1,=pi
vldr s1,[r1] @ s1 contient
pi
et dans ce cas il est inutile d’effectuer la conversion par
l’instruction vcvt.
Exercice : tester
un générateur diffèrent pour avoir un khi meilleur
Améliorer
la routine de conversion du nombre en simple précision
Et plus
difficile, écrire une routine de conversion pour les nombres en double
précision.
Et aussi
une routine pour saisir dans la console des nombres en virgule flottante.