FLOATING POINT PACKAGE (F.P.P.)
Le FPP est un ensemble de routines permettant d'effectuer des calculs "complexes" que l'on ne peut donc pas faire "simplement" avec les instructions arithmétiques du 6502. Ces routines permettent de travailler sur des nombres à virgules. Ils peuvent être bien plus grand que ce que peuvent contenir les registres du 6502. Le FPP va donc mettre en oeuvre des traitements plutôt lourds. Pour cette raison, il vaut mieux éviter d'utiliser ces routines autant que possible dans un jeu, car le traitement peut-être très long et le jeu d'autant plus lent.
Représentation Interne d'un nombre:
Un nombre est codé sur 6 octets.
Le premier octet se décompose de la manière suivante:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
0 - Positif 1 - Négatif |
Signe de l'exposant (1 si positif, 0 si négatif)
|
Exposant pour une puissance de 100 |
L'exposant va permettre de placer la virgule sur le nombre. A noter que si l'exposant est inférieur à 64, ce nombre est alors inférieur à 1 et inversement. 64 est l'origine du repère (le 0). Tous nombres supérieurs à 64 est un exposant positif, tous nombres inférieurs est un nombre négatif.
L'exposant +2 s'écriera donc 64 + 2 = 66.
L'exposant -2 s'écriera 64 - 2 = 62.
Les 5 octets suivants sont la mantisse de du nombre au format BCD (Binary Coded Decimal):
Par exemple 25 sur 1 octet serait codé 2 sur les 4 bits fort de l'octet, et 5 sur les 4 bits de poids faibles.
Astuce: pour lire le nombre sans difficultés, il suffit d'afficher la valeur de la mantisse en hexadécimal (4 bits occupés par chiffre, c'est exactement ce qui se passe en hexa !).
Voici un exemple que j'ai récupéré, je ne sais plus où:
Number: 0.02 2 * 100**-l
Format: 3F 02 00 00 00 00 (exponent= 40-1)
Number: -0.02 -2 * 100**-l
Format: BF 02 00 00 00 00 (exponent= 80+40-1)
Number: 37.0 = 37 * 100**0
Format: 40 37 00 00 00 00 (exponent= 40+0)
Number: -460312 = -46.0312 * 100**2
Format: C2 46 03 12 00 00 (exponent= 80+40+2)
The number zero is handled as a special case, and is represented as a zero exponent and zero mantissa. Either the exponent or the first mantissa byte may be tested for zero.
The dynamic range of numbers that can be represented by this scheme is 10**-98 to 10**+98.
Implantation en mémoire:
Le code FPP est localisé aux adresses $D800 à $DFFF.
Il utilise ses propres registres (ou plutôt des pseudos registres) et comme nous l'avons vu ci-dessus, sa propre méthode d'écriture d'un nombre. Il a donc besoin de mémoire pour tout cela:
Deux zones mémoires qui sont:
$00D4 - $00FF
$057E - $05FF
Si vous n'utilisez
pas le FPP, cette zone mémoire est alors libre. Attention, ceci n'est
vrai que si vous n'utilisez pas le Basic avec votre
programme machine. Car sinon, Basic lui utilisera cette zone pour les variables,
lire une chaîne de caractères...
Il y a deux pseudos-registres nommés FR0 et FR1. Ces registres pouvant stocker des nombres à virgules flottants, ils occupent donc 6 octets.
FR0 est localisé en $D4 à D9 et FR1 est localisé en $E0 à $E5.
Il existe aussi un pointeur nommé FLPTR (sur 2 octets) sur une zone mémoire contenant un nombre flottant. Ce pointeur est situé à l'adresse $FC et $FD. Ce pointeur permettra de charger l'un des deux pseudos-registres avec un nombre flottant stocké dans une zone mémoire différente ou de stocker un nombre flottant du registre FR0 vers une zone mémoire.
De même, on peut avoir besoin d'afficher des nombres flottants à l'écran. Dans ce cas, il faudra utiliser des routines de conversion de nombres flottants en ATASCII (qui, heureusement, existent déjà).
La zone mémoire utilisée s'appelle LBUFF, et occupe 128 octets situés entre $0580 à $05FF.
Cette zone mémoire
servant aussi au BASIC, ne pas y laisser un nombre en espérant le récupérer
plus tard.
Un pointeur est utilisé pour pointer sur cette zone mémoire : INBUFF en $F3 et $F4.
Enfin, un index nommé CIX, situé en $F2, utilisé comme offset avec le pointeur INBUFF. Ne sert normalement pas pour nous et est à mettre à zéro.
Routines du FPP:
AFP: Conversion ATASCII en nombre flottant
Il faut indiquer dans INBUFF la zone mémoire où se trouve le nombre au format ATASCII. La chaîne de caractères devra se terminer par le code CR au format ATASCII:$9B.
CIX doit être à 0 si vous pointez déjà au début de votre nombre dans INBUFF...
FASC: Conversion d'un nombre flottant en ATASCII
Conversion d'un nombre flottant contenu dans FR0 en ATASCII. Le résultat est stocké dans LBUFF, INBUFF est initialisé.
Le bit 7, dans
le cas où vous comptez les bits de 0 à 7, sinon se sera le bit
8 (1 à 8 !) du dernier caractère est positionné à
1. De plus, il n'y a pas de code CR.
IFP: Conversion d'un entier non signé (16 bits) en nombre flottant
FR0 contient ce nombre entier (les 2 premiers octets de FR0).
En sortie, FR0 est un nombre flottant.
FPI: Conversion d'un nombre flottant en entier (16 bits)
FR0 contient un nombre flottant.
En sortie, les deux premiers octets de FR0 représentent un nombre sur 16 bits.
CARRY est positionné si overflow (si résultat > 65535).
FADD: Addition sur deux nombres flottants
FR0 et FR1 contiennent des nombres flottants
L'opération est FR0 + FR1
Le résultat de cette addition sera stocké dans FR0.
CARRY est positionné si les valeurs sont hors limites (out of range).
FSUB: soustraction entre deux nombres flottants
FR0 et FR1 contiennent les deux nombres flottant à soustraire.
L'opération est FR0 - FR1
Le résultat de cette soustraction sera stocké dans FR0.
CARRY est positionné si les valeurs sont hors limites (out of range).
FMUL: Multiplication de deux nombres flottants
FR0 et FR1 contiennent les deux nombres flottant à multiplier.
L'opération est FR0 * FR1
Le résultat de cette opération sera stocké dans FR0.
CARRY est positionné si les valeurs sont hors limites (out of range).
FDIV: Division de deux nombres flottants
FR0 et FR1 contiennent les deux nombres flottant à Diviser.
L'opération est FR0 / FR1
Le résultat de cette opération sera stocké dans FR0.
CARRY est positionné si les valeurs sont hors limites ou si le diviseur (FR1) = 0.
Je n'ai jamais lancé les routines de calcules qui suivent...
LOG: Logarithme
LOG10: logarithme base 10
FR0 contient le nombre flottant
En sortie, FR0 est le résultat de l'opération.
CARRY est positionné si le nombre est négatif ou dépasse la valeur maximale.
EXP: Exponentiel
EXP10: idem en base 10
FR0 contient le nombre flottant
En sortie, FR0 est le résultat de l'opération.
PLYEVL: Evaluation polynomiale
Le registre X doit contenir la partie basse d'un tableau sur des nombres flottants.
Le registre Y doit contenir la partie haute de ce même tableau.
Le registre A Contient le nombre d'élément dans le tableau.
En sortie: Le résultat est dans FR0.
CARRY est positionné s'il y a une erreur de dépassement capacité (overflow).
De mémoire (très vieille) de mes cours de math., il existe une fonction polynome pour avoir une valeur proche de la fonction cosinus. Je suppose que c'est cette fonction PLYEVL qu'il faut utiliser.
Cependant, les calculs seront alors bien lourd. Alors autant utiliser la fonction Basic COS. En assembleur, si vous voulez l'utiliser pour afficher du graphisme, il vaut mieux passer par une table des cosinus, se sera bien plus rapide.
ZFR0: Effacer le contenu de FR0
En sortie, les octets de FR0 sont à 0
ZF1: Effacement de la zone d'un nombre flottant en page zéro
En entrée, le registre X contient l'adresse en page 0 (la partie basse, On ne renseigne rien d'autre puisque le poids fort est à 0)
FLD0R: Charger FR0 avec un nombre flottant.
En entrée, Les registres X et Y pointent sur une zone mémoire contenant un nombre flottant (X = poids faible, Y = poids fort).
En sortie, FLPTR pointe sur cette zone mémoire et FR0 contient le nombre flottant.
FLD0P: Charger FR0 avec un nombre flottant.
En entrée, FLPTR pointe sur une zone mémoire contenant un nombre flottant.
En sortie, FR0 contient le nombre flottant.
FLD1R: Charger FR1 avec un nombre flottant.
En entrée, Les registres X et Y pointent sur une zone mémoire contenant un nombre flottant (X = poids faible, Y = poids fort).
En sortie, FLPTR pointe sur cette zone mémoire et FR1 contient le nombre flottant.
FLD1P: Charger FR1 avec un nombre flottant.
En entrée, FLPTR pointe sur une zone mémoire contenant un nombre flottant.
En sortie, FR1 contient le nombre flottant.
FST0R: Stocker le contenu de FR0 dans une zone mémoire.
En entrée, Les registres X et Y pointent sur une zone mémoire (X = poids faible, Y = poids fort).
En sortie, FLPTR pointe sur cette zone mémoire et cette zone contient ce nombre flottant.
FST0P: Stocker le contenu de FR0 dans une zone mémoire.
En entrée, FLPTR pointe sur une zone mémoire qui contiendra le nombre flottant.
En sortie, la zone contient ce nombre flottant.
FMOVE: Déplacer le contenu de FR0 dans FR1
En entrée, FR0 contient un nombre flottant qui sera transféré dans FR1.
En sortie, FR1 est chargé...
Tableau récapitulatif:
LISTE DES ROUTINES DU F.P.P.
NOM |
ADDRESSE |
FONCTION |
RESULTAT |
TEMPS MAX. OCCUPATION
DU CPU |
AFP | D800 | Conversion ATASCII en nbre flottant | FR0 | 3500 |
FASC | D8E6 | Conversion nbre flottant en ATASCII | LBUFF | 1000 |
IFP | D9AA | Conversion d'un entier (16bits) en nbre flottant. | FR0 | 1300 |
FPI | D9D2 | Conversion nbre flottant en entier | FR0 | 2400 |
FSUB | DA60 | Soustraction : FR0-FR1 | FR0 | 750 |
FADD | DA66 | Addition : FR0+FR1 | FR0 | 750 |
FMUL | DADB | Multiplication : FR0*FR1 | FR0 | 12000 |
FDIV | DB28 | Division : FR0/FR1 | FR0 | 10000 |
FLD0R | DD89 | Charger un nombre flottant avec X,Y | FR0 | 70 |
FLD0P | DD8D | Charger un nombre flottant avec FLPTR |
FR0 | 60 |
FLD1R | DD98 | Charger un nombre flottant avec X,Y |
FR1 | 70 |
FLD1P | DD9C | Charger un nombre flottant avec FLPTR | FR1 | 60 |
FSTOR | DDA7 | Stocker un nombre flottant avec X,Y | FR0 | 70 |
FSTOP | DDAB | Stocker un nombre flottant avec FLPTR | FR0 | 70 |
FMOVE | DDB6 | Déplacer le contenu de FR0 | FR1 | 60 |
ZFR0 | DA44 | Initialiser FR0 | FR0 | 80 |
ZF1 | DA46 | Vider une zone mémoire d'un nombre flottant | Zone mémoire indiquée | 80 |
PLYEVL | DD40 | Evaluation Polynomiale | FR0 | 88500 |
EXP | DDC0 | Exponentiel | FR0 | 116000 |
EXP1O | DDCC | Exponentiel base 10 | FR0 | 109000 |
LOG | DECD | Logarithme | FR0 | 136000 |
LOG10 | DED1 | Logarithme Base 10 | FR0 | 125400 |
Exemple:
Voici un exemple en assembleur:
On part d'une chaîne de caractères (dont le nombre est 3) et d'un entier sur 16 bits dont la valeur est 5
Opération 3+5
Le résultat est multiplié à un nombre flottant (20). Ce résultat de l'opération est converti en entier puis retourné dans y du y = usr().
Le source:
Le binaire:
Et le programme pour le charger en Basic:
Voici un second exemple. Dans celui-ci, on calcul un score:il démarre arbitrairement à 123 et on ajoute 1 10 fois de suite...
Ce code va en plus afficher le résultat du score à l'écran (Utilisation de FASC) directement à l'aide d'une procédure perso. dans la zone mémoire de l'écran.
Le source:
Le binaire:
Et le programme pour le charger en Basic: