Site logo

Triceraprog
La programmation depuis le Crétacé

  • VG5000µ, deux routines (quasi) identiques ()

    Hier, en continuant le commentaire systématique de la ROM du VG5000µ, j'ai eu une impression de déjà-vu. Il me semblait bien avoir déjà commenté la partie « redéfinir un caractère étendu » (commandes SETE et SETG en BASIC).

    L'impression persiste, voire s’accroît, au fur et à mesure et je me décide à aller voir dans ce que j'ai déjà commenté. J'ai commencé, lentement, ce commentaire en août 2017, avec moult pauses, mais aussi du « butinage ». Il y a donc des parties que je ne me rappelle plus avoir déjà traitées.

    Et effectivement, un peu plus loin, je vois des commentaires similaires à ceux que je suis en train d'écrire. Les termes sont un peu différents, car au fil des commentaires, j'ai fait évoluer certains termes ou la manière de commenter, mais en regardant de plus près, oui, c'est évident, il y a deux fois la même routine dans la ROM.

    Je ne sais pas si c'est quelque chose de connu, alors voici mes commentaires.

    Le VDP

    Le Video Display Processor du VG5000µ, un EF9345, est utilisé par le BASIC de manière généralement indirecte. Les commandes comme les changement de couleur, de type de caractères ou encore de position du curseur, modifient des registres internes en RAM. Ces registres sont juste des emplacements réservés dans la RAM principale, celle accessible au Z80, ils n'ont rien de spécial.

    Ces registres se trouvent à partir de $47fa et pointés en tout temps par le registre IX.

    Le BASIC maintient aussi une représentation de l'écran, en $4000, et afficher quelque chose en BASIC revient à modifier cette représentation d'écran, en se basant sur les registres BASIC.

    À chaque rafraîchissement demandé par le VDP, via l'IRQ du Z80 et si le BASIC considère qu'il est temps de mettre à jour l'affichage, un transfert des données est fait vers le VDP.

    C'est le fonctionnement de base.

    Cependant, la ROM contient aussi des routines qui s'adressent directement au VDP. On peut envoyer un caractère dans la mémoire écran du VDP sans passer par la représentation maintenue par le BASIC. Bien évidemment, si on laisse faire le rafraîchissement du BASIC, cette donnée sera écrasée rapidement. On peut aussi lire un caractère depuis la mémoire écran du VDP.

    Ces quatre routines, putahl, putici, getahl et getici ne sont jamais utilisées par la ROM. Ce sont des routines mises à disposition pour l'utilisateur à une adresse fixe, dont le seul code est un branchement à l'implémentation.

    Une autre routine mise à disposition est setext, qui s'occupe de la redéfinition d'un caractère texte ou graphique dans le VDP. Le VDP est en effet configuré par défaut pour offrir 4 « polices de caractères », dont 2 en ROM, et 2 en RAM (celle accessible directement par le VDP). Les deux en RAM peuvent être modifiées via cette routine setext en fournissant le numéro du caractère à changer et 10 octets qui représentent les 10 lignes d'affichage du caractère.

    setext se trouve en $001b et branche immédiatement sur son implémentation en $0d85.

    setext vs. SETE(T/G)

    Le BASIC offre deux commandes pour redéfinir les caractères : SETET, pour redéfinir un caractère texte, et SETEG, pour redéfinir un caractère graphique. La différence entre caractère texte et graphique sort du périmètre de cet article.

    Du point de vue de l'interpréteur BASIC, il n'y a qu'une seule commande SETE, qui vérifie si le caractère suivant est T ou G. On pourrait dire que la dernière lettre de la commande est vue par l'interpréteur comme son premier paramètre.

    L'exécution de SETE se trouve en $0ced dans la ROM BASIC.

    On pourrait s'attendre à ce que SETE utilise la routine setext. Ou bien que les deux routines aient une partie commune. Ce n'est pas le cas.

    Des 141 octets de l'implémentation de setext, environ 100 (à la louche) sont strictement identiques dans SETE, routine qui elle pèse 160 octets (en incluant sa routine annexe, qu'elle est la seule à appeler).

    Différences

    Les deux routines font la même chose, dans le même ordre :

    • Couper le rafraîchissement de l'écran depuis sa représentation RAM,
    • Déterminer si on veut un caractère graphique ou texte,
    • Récupérer le numéro du caractère,
    • Envoyer les commandes au VDP pour préparer la définition,
    • Envoyer les 10 lignes au VDP,
    • Rétablir le rafraîchissement de l'écran.

    Pour setext, c'est très simple, les informations sont dans le registre A pour le caractère et son type, et HL pointe vers les données.

    Pour SETE, c'est un peu plus complexe. Le type de caractère est déterminé par la présence de T ou G, il y a une vérification de la validité du premier paramètre de la commande (numéro de caractère), puis la chaîne de caractères de description, suite de nombres hexadécimaux en ASCII, doit être décodée. C'est la raison pour laquelle la routine est plus longue.

    Cependant, le corps de la routine qui envoie toutes les commandes est identique, à la récupération de la donnée prêt. Et bien que les deux routines utilisent bien les mêmes appels pour envoyer une commande au VDP ou attendre que celui-ci soit prêt à recevoir une commande, il doit bien y avoir une centaine d'octets pouvant être mis en commun.

    Pourquoi ?

    Je n'ai pas d'explication à la duplication de cette routine. Il reste un peu de place inutilisé dans la ROM, et peut être qu'il n'était plus nécessaire d'optimiser la place prise et que c'est juste resté « comme ça ».

    Ce n'est pas, à mon avis, une question de performances avec setext qui serait plus rapide. Elle l'est, en effet, mais pourrait l'être encore plus. En effet, setext garde de SETE le fait de « retourner » chaque octet des descriptions de ligne pour les passer d'une visualisation humaine au format demandé par le VDP. La suite de commandes à envoyer pourraient aussi être rendue plus rapide à l'aide d'un buffer de commandes et l'utilisation de la routine regst.

    Amélioration ?

    Une amélioration possible de la ROM serait de réécrire ces deux routines. Il y aurait peut-être même moyen de profiter de la place gagnée pour caser deux commandes PSET et PRESET, qui font cruellement défaut sur le VG5000µ.


  • Considérations sur le langage LOGO ()

    Quand je vois passer des mentions du langage LOGO, elles sont généralement peu flatteuses. C'est compréhensible, la plupart des personnes qui se souviennent de ce langage de programmation y ont été exposées pendant leurs jeunes années d'études, lors du Plan Informatique pour Tous. Peu sont ceux qui ont creusé plus tard ce qu'ils avaient découverts au moyen d'ordinateurs poussifs et de cours pas toujours maîtrisés par des enseignants pas toujours bien convaincus.

    Mes quelques souvenirs de séances en salle informatiques sont plutôt sur des activités de manipulation du crayon optique sur « Colorpaint ». Je ne sais plus si j'ai fait du LOGO en classe, mais c'est probable. Je me souviens cependant avec netteté la rencontre avec un professeur, lors de la fréquentation d'un « club informatique » pendant des vacances, qui pour une raison ou une autre (mon intérêt enthousiaste à la programmation ?) m'a donné quelques cours de LOGO. Et lors d'une conversation dans laquelle j'avais du mentionner que je programmais en BASIC, il m'a dit cette phrase qui est restée gravée dans ma mémoire « le LOGO est bien plus puissant que le BASIC ».

    Je me souviens que cette phrase m'avait intriguée, mais pas forcément convaincue. Et de retour chez moi, j'ai continué mes aventures en BASIC. J'ai croisé LOGO quelques autres fois, de loin, sans m'y attarder.

    Ce n'est que beaucoup plus tard, quand j'ai commencé à faire de la programmation mon métier, que j'ai repensé à cette phrase. J'ai regardé ce qu'était LOGO de manière plus expérimentée et j'ai compris. J'ai compris la justesse de l'assertion. LOGO était bien autrement plus puissant que le BASIC. Mais d'une manière que je ne pouvais pas comprendre auparavant.

    C'est que la notion de « puissance » appliquée à un langage, ainsi qu'à son environnement, revêt de nombreux aspects. Cela peut être la vitesse d'exécution du programme, la facilité d'écriture, les possibilités d'interactivité avec la machine, la diversité des structures de code et de données.

    Sur des ordinateurs 8 bits, la vitesse d'exécution du BASIC l'emporte haut la main, même sur des BASIC interprétés comme ils l'étaient en majorité.

    Sur la facilité d'écriture, le BASIC est aussi très simple. C'était son but initial. LOGO n'est pas particulièrement complexe à écrire, mais comporte quelques étrangetés syntaxiques et pièges dont je parlerai dans un autre article. Et LOGO est aussi un peu plus verbeux.

    En possibilité d'interaction avec la machine, BASIC l'emporte probablement. Tous les LOGO ne se valent pas, mais l'orientation pédagogique sur les machines 8 bits poussaient les concepteurs à offrir essentiellement des manipulations de l'écran via la tortue, et quelques sons. BASIC, en tant que porte d'entrée de la machine, était souvent dotés des instructions nécessaires pour en démontrer ses capacités. Avec de notables exceptions cependant, mais ceci est une autre histoire.

    Vient ensuite la structure de code et de données. Et c'est là que LOGO renverse BASIC d'une pichenette. Le BASIC de l'époque est très linéaire, son édition se fait encore par numéro de lignes, sans labels. La structuration est possible bien entendu, mais les outils sont maigres. Niveau structure de donnée, le BASIC connaît le tableau multidimensionnel de taille fixe... et c'est tout.

    LOGO arrive avec ses fonctions qui supportent la récursivité, avec des contextes locaux, un nommage des procédures et une édition sans numéro de lignes, qui facilite les mises au point.

    Côté données, LOGO connaît la liste. Et avec une liste dynamique, de nouveaux horizons s'étendent. Des listes de nombres, des listes de mots, des listes de listes, des listes de commandes à exécuter...

    L'expressivité du langage est beaucoup plus intéressante, et les programmes plus concis, plus clairs. C'est en ce sens que LOGO était bien plus puissant.

    Mais tout ceci nécessite de la puissance de calcul et de la mémoire que les machines familiales de l'époque n'ont pas. Et cette malheureuse empreinte qui va rester principalement : on ne fait rien de bien sérieux avec LOGO.

    Pour donner un petit aperçu de LOGO et de son évolution, j'ai réalisé une petite vidéo (moins de 10 minutes) que vous pouvez voir ici.

    (billet posté aussi sur mon blog perso).


  • Récréation 3D, Cartouche Atari 800 ()

    J'avais une cartouche « Atari Writer » qui traînait sur le bureau depuis quelques temps, et je voulais faire un exercice rapide de modélisation avec Blender.

    Cela donne cette image, assez simple, mais qui a été un bon exercice.

    Tortue Jeulin T2


  • VG5000µ, nombres aléatoires ()

    Comment donc les nombres aléatoires sont-ils générés sur un VG5000µ. C'est ce que je vous propose de suivre aujourd'hui en décortiquant le code.

    Afin de suivre, il est important de comprendre comment les nombres sont stockés sur VG5000µ, et je vous propose pour cela un petit détour par cet article.

    Petit rappel avant de commencer : un générateur de nombres aléatoires est une procédure qui émet une suite de nombres sur un intervalle, cette suite tentant d'avoir des propriétés intéressantes qui donnent l'illusion de l'aléatoire. La suite est cependant parfaitement définie, même si pas toujours simple à suivre, et c'est ce que nous allons voir par la suite.

    L'initialisation

    Tout commence très tôt pour le générateur de nombres aléatoires. Dès l’initialisation de la machine, une série de valeurs est copiée depuis la ROM vers les variables systèmes. Cela se passe en $1071, juste après l'initialisation de l'affichage.

                 ld       hl,initvalues
                 ld       bc,$0065
                 ld       de,ramlow
                 ldir
    

    Il y a donc 101 valeurs (\(65) copiées depuis initvalues ($1194) vers ramlow (\)4830). Parmi celles-ci, les suivantes sont copiés vers $4844 et nous intéressent aujourd'hui.

                 defb     $00,$00,$00
                 defb     $35,$4a,$ca,$99
                 defb     $39,$1c,$76,$98
                 defb     $22,$95,$b3,$98
                 defb     $0a,$dd,$47,$98
                 defb     $53,$d1,$99,$99
                 defb     $0a,$1a,$9f,$98
                 defb     $65,$bc,$cd,$98
                 defb     $d6,$77,$3e,$98
                 defb     $52,$c7,$4f,$80
    

    Les trois premiers octets sont les trois index avec lesquels le générateur va jouer. Nous les appellerons les trois seeds. Suivent 8 nombres plutôt grands, positifs et négatifs (le premier vaut -26514538). Et enfin vient le nombre d'origine, qui vaut 0.8116351366043091, que vous pouvez retrouver sous sa forme arrondie en tapant PRINT RND(0) dès l'allumage du VG5000µ.

    Tête sur VG5000µ

    Cette table n'est pas la seule qui va être utilisée par le générateur. Il en existe une autre, qui sera utilisée depuis la ROM, en $093d, d'une longueur de 3.

                 defb     $68,$b1,$46,$68
                 defb     $99,$e9,$92,$69
                 defb     $10,$d1,$75,$68
    

    L'instruction RND

    L'instruction RND commence en $090d par un petit préambule qui vérifie l'argument passé à la fonction. Cet argument est disponible dans FAC, l'accumulateur flottant (voir précédemment)

    inst_rnd:    rst      getsign
                 ld       hl,rnd_seed_2
                 jp       m,reseed
                 ld       hl,rnd_gen
                 call     hl_to_fac
                 ld       hl,rnd_seed_2
                 ret      z
    

    Ce préambule teste en premier lieu le signe de l'argument. S'il est négatif, la routine branche vers reseed, que nous verrons plus loin. C'est au passage un comportement qui n'est indiqué ni dans le manuel d'utilisation du VG5000µ, ni dans les « Clés pour VG5000 », qui donnent de fausses informations (je vous laisse regarder).

    Dans le cas du branchement, HL point vers la seed 2 (et je ne vois aucun intérêt à ce que cela ne soit pas fait après le branchement...)

    Si l'argument est nul ou positif, alors la nombre pointé par HL, qui est la variable système du dernier nombre généré ,est copié dans FAC. Souvenez-vous, c'est à cette adresse qu'a été placé à l'initialisation le nombre 0.8116351.

    On refait pointer HL vers la seed 2 puis, si l’argument était 0, on sort de la routine immédiatement (le flag Zéro a été conservé depuis rst getsign).

    Le calcul

    Puisqu'on est à présent dans le cas où l’argument est positif, il convient de générer un nouveau nombre. Ce nouveau nombre est basé sur, d'une part, le nombre généré précédemment, et d'autre part, les trois index qui avaient été initialisés à zéro (voir ci-dessus).

    Première étape
                 add      a,(hl)
                 and      a,$07
                 ld       b,$00
                 ld       (hl),a
                 inc      hl
    
                 add      a,a
                 add      a,a
                 ld       c,a
                 add      hl,bc
                 call     hl_to_bcde
                 call     fp_mul
    

    La première section du code précédent récupère l'index seed 2 en l'incrémentant de 1. En effet, A contient 1 depuis l'appel à getsign. Le résultat est pris modulo 8 et remonté en RAM.

    Au passage, B est initialisé à 0 pour que BC puisse servir d'index, et HL va pointer un cran plus loin, sur le début de la table des coefficients initialisés au boot (la table des 8 valeurs).

    La seconde section calcul le pointeur dans cette table en quadruplant A, qui est l'index, en format BC comme index à ajouter au pointeur de base HL.

    L'appel hl_to_bcde copie le nombre pointé dans la table vers BCDE, puis l'appel à fp_mul effectue la multiplication avec le contenu de FAC.

    Résumé : cette première étape est donc une multiplication du précédent nombre généré par un autre nombre, fixe, pris dans une table dans 8 valeurs tour à tour.

    Le tout premier appel à RND(1) va multiplier 16129081 ($98 $76 $1c $39) avec0.8116351366043091($80 $4f $c7 $52`).

    Cela donne 13090929 ($98 $47$c0 $71). Cela peut se vérifier dans FAC ($49e6). Attention, le nombre est octet par octet dans le sens inverse à celui que j'utilise ici.

    Seconde étape
                 ld       a,(rnd_seed_1)
                 inc      a
                 and      a,$03
                 ld       b,$00
                 cp       a,$01
                 adc      a,b
                 ld       (rnd_seed_1),a
    
                 ld       hl,rnd_add - 4
                 add      a,a
                 add      a,a
                 ld       c,a
                 add      hl,bc
                 call     fp_add_hl
    afterreseed: call     fac_to_bcde
    

    La seconde étape se divise elle aussi en deux sections.

    Dans la première section, on récupère la seed 1, qui est incrémentée de 1 et modulo 4. Cependant, la valeur 0 est interdite. Par une comparaison avec 1 et un ajout à 0 (via B) avec retenue, si l'index était à 0, alors il est poussé à 1.

    C'est donc en fait un index modulo 3 que l'on obtient.

    Et cet index forme un pointeur via HL de manière similaire à l'étape précédente, dans la table de trois valeurs de la ROM mentionné au début de l'article.

    Cette valeur est alors ajoutée à FAC. L'appel à fp_add_hl se charge de l'étape intermédiaire de chargement de la valeur dans BCDE. Puis le résultat est ramené dans BCDE.

    Le label est un branchement venant du reseed que nous verrons plus loin.

    Résumé : cette seconde étape est une addition du nombre obtenue à la première étape avec un des trois nombres pris dans la deuxième table, pris tour à tour.

    Le tout premier appel additionne 13090929 ($98 $47 $c0 $71 ) avec 4.626181e-08 ($68 $b1 $46 $68). Ce second nombre est bien trop petit par rapport au premier. Cette addition ne change rien... dans ce cas-ci. Nous verrons plus tard à quoi cette addition peut servir.

    Troisième étape

                 ld       a,e
                 ld       e,c
                 xor      a,$4f
                 ld       c,a
    

    Dans cette troisième étape, le générateur fait des mélanges. Les 8 bits de poids les plus faibles sont mis de côté et les 8 bits de poids fort sont placés dans les 8 bits de poids faible.

    Les 8 bits mis de côté sont XORés avec b01001111. Ce qui signifie que certains bits sont inversés. Puis le résultat est placé dans les 8 bits de poids fort.

    Cette opération ne me semble pas avoir de sens arithmétique. Cela semble être juste un mélange. Peut-être pour amener de l'entropie dans les bits de poids faible... Peut-être.

    Résumé : cet étape mélange les parties de la mantisse et change quelques bits.

    Le nombre est à présent 12501063 ($98 $3e $c0 $47).

    Étape intermédiaire

                 ld       (hl),$80
                 dec      hl
                 ld       b,(hl)
                 ld       (hl),$80
    

    Cette étape profite que HL pointe actuellement (depuis la récupération de FAC dans BCDE) sur l'octet après FAC pour préparer le terrain pour plus tard. Cet octet contient le complément à 1 du bit de signe du nombre de FAC. En mettant cet octet à $80, on force la valeur à être positive.

    HL pointe ensuite sur l'octet précédent, l'exposant, et met celui-ci à $80, c'est-à-dire \(2^ 0\) (voir l'article sur les nombres, toujours).

    Résumé : le nombre final sera un nombre positif et l'exposant est fixé à \(2^0 = 1\).

    Pas d’influence, pour le moment sur le nombre tenu dans BCDE.

    Quatrième étape*

                 ld       hl,rnd_seed_0
                 inc      (hl)
                 ld       a,(hl)
    
                 sub      a,$ab
                 jr       nz,rnd_cnt
                 ld       (hl),a
                 inc      c
                 dec      d
                 inc      e
    

    C'est la dernière étape du calcul. Dans la première partie, la seed 0 est incrémentée et récupérée dans A.

    Si la soustraction par 171 ($ab) n'est pas nulle, on branche plus loin à l'étape finale. Sinon, le résultat (0) est replacé dans la seed 0. C'est donc un compteur jusqu'à 171 qui, lorsqu'il atteint cette valeur, modifie légèrement la mantisse.

    Le premier et le troisième octets de la mantisse sont incrémentés, celui du milieu décrémenté, sans se soucier de débordements éventuels.

    Résumé : une fois tous les 171 tirages, la mantisse est modifiée légèrement.

    Comme ici, c'est le premier tirage, il ne se passe rien.

    Étape finale

    rnd_cnt:     call     bcde_norm
                 ld       hl,rnd_gen
                 jp       cpy_faclsb_hl
    

    La mantisse a été générée, l'exposant est . Mais tout nombre dans FAC en sorti de routine doit être normalisé.

    Comme indiqué dans l'article précédent sur la représentation des nombres, cela signifie que la mantisse et l'exposant vont être modifiée afin d'obtenir une mantisse avec un premier bit à 1 implicite, lui-même remplacé par le bit de signe.

    MAIS ! Il y a un twist ! La routine bcde_norm n'attend pas en entrée un nombre BCDE, mais une mantisse 32 bits CDEB. L'exposant du nombre actuel va donc se retrouver... en partie la moins significative du nombre, afin de nourrir, en quelque sorte, la partie droite de la mantisse lors de l'éventuel décalage vers la gauche.

    C'est peut-être un peu obscure : je donne un exemple dans le résumé.

    En sortie de normalisation, le nombre est bien dans FAC. Le contenu de FAC est alors copié à l'emplacement du dernier nombre généré.

    C'est terminé !

    Résumé : mise en forme du nombre, à la fois dans FAC comme résultat de la fonction, et de côté pour servir de base au prochain nombre généré (ou pour être retourné en case de RND(0)).

    Nous en étions à $98 $3e $c0 $47. Mais la normalisation s'attend à une mantisse 32 bits, et c'est donc comme ça que va être perçue la mantisse : $3e $c0 $47 $98.

    La normalisation doit déplacer la mantisse à gauche jusqu'à ce que le bit de poids fort soit à 1. Il va falloir deux étapes pour cela :

    • de $3ec04798 à $7d808f30, puis
    • de $7d808f30 à $fb011e60

    Comme le bit de poids fort du dernier octet n'est pas à 1, il n'y a pas d'arrondi. Cet octet est abandonné, le bit de signe et l'exposant corrigé par le nombre d'étapes ($80 - 2 donne $7e).

    Au final, nous avons obtenu : $7e $7b $01 $1e, soit 0.245121

    Tête sur VG5000µ

    Suppléments

    Re-seed

    Si l'argument de RND() est négatif, alors un branchement a lieu sur une routine de réinitialisation du générateur.

    reseed:      ld       (hl),a
                 dec      hl
                 ld       (hl),a
                 dec      hl
                 ld       (hl),a
                 jr       afterreseed
    

    À l'arrivée dans cette partie, HL pointe sur seed 2 et A est égal à $FF. Ce qui a pour résultat de mettre les trois octets à $FF.

    Le branchement ramène dans le générateur à la fin de la seconde étape, c'est-à-dire après la multiplication et l'addition. Ce qui est dans FAC (l'argument négatif de RND()) est ramené dans BCDE et le reste des étapes est effectué.

    C'est donc une nouvelle séquence qui démarre, dépendante de l'argument passé à RND().

    Cas de l'addition

    Revenons sur la seconde étape, l'addition avec un nombre tout petit. Dans l'exemple que nous avons suivi pendant l'article, l'addition ne servait à rien, car la différence entre les exposants était trop grand et donc le nombre à addition non significatif.

    La question à se poser est donc : dans quels cas ces nombres deviennent-ils significatifs ?

    Le plus petit d'entre eux est : $68 $46 $b1 $68 qui a pour exposant $68. C'est-à-dire $80 - 24.

    Il faut donc un nombre strictement inférieur à $00 $00 $00 $80 (0.5) pour que l'addition soit intéressante.

    Sauf que... juste avant l'addition, la multiplication a été faite avec un nombre dont l'exposant était au minimum $98. Puisque dans une multiplication, les exposants s'ajoutent, cela implique que seuls des nombres avec un exposants à '$68' initialement vont être assez petits après la multiplication et être modifiés par l'addition.

    C'est quelque chose de facile à déclencher en modifiant à la main le dernier nombre généré en mémoire. Mais est-ce que cela se passe si on laisse le générateur se dérouler normalement ?

    Un expérience simple avec un debuggeur en mettant un point d'arrêt dans le code d'addition et en faisant tourner le générateur montre que... non. Cela n'arrive pas. Ou alors assez rarement pour résister à l'expérience.

    Il est temps de se poser la question des bornes maximales et minimales des nombres générés.

    Mais vu la longueur de l'article, ce sera pour le prochain...


  • VG5000µ, jouer avec les nombres ()

    Suite à l'article précédent, j'ai mis sur le dépôt GitHub un petit utilitaire Python qui reproduit les conversions entre la valeur du nombre et son codage en 4 octets.

    Parfois, voir du code est plus simple qu'un long discours.

    Et parce qu'on ne sait jamais trop quel sera la vie future du dépôt, voici le code des deux principales fonctions de l'outil.

    import math
    
    
    def get_byte(number):
        """Takes the current number, and returns the next byte encoding it with the reminder of the number to encode. """
    
        number *= 256
        result = int(number)
        return result, (number - result)
    
    
    def encode(number):
        """Gets a number, returns it's encoded four bytes (memory layout, so exponent at the end)."""
    
        # If the number is zero, the encoding is immediate.
        # In fact, only the exponent has to be 0.
        if number == 0:
            return [0, 0, 0, 0]
    
        # Gets the sign from the number for later encoding
        sign = 0x80 if number < 0 else 0
    
        # We encode only positive numbers
        number = abs(number)
    
        # Shift the number so that the first fractional part bit
        # of the mantissa is 1 (0.1 binary is 0.5 decimal)
        exp = 0
        while number >= 0.5:
            number /= 2
            exp += 1
        while number < 0.5:
            number *= 2
            exp -= 1
    
        # Gets the three bytes encoding the mantissa
        o1, number = get_byte(number)
        o2, number = get_byte(number)
        o3, number = get_byte(number)
    
        # Clears the most significant bit
        # and replace it by the sign bit
        o1 &= 0x7F
        o1 |= sign
    
        # Encode exponent
        exp += 128
    
        # Returns an array (Z80 memory layout)
        return [o3, o2, o1, exp]
    
    
    def decode(encoded):
        """ Takes four encoded bytes in the memory layout, and returns the decoded value. """
    
        # Gets the exponent
        exp = encoded[3]
    
        # If it's 0, we're done. The value is 0.
        if exp == 0:
            return 0
    
        # Extract value from the exponent
        exp -= 128
    
        # Extract the sign bit from MSB
        sign = encoded[2] & 0x80
    
        # Sets the most significant bit implied 1 in the mantissa
        encoded[2] = encoded[2] | 0x80
    
        # Reconstruct the mantissa
        mantissa = encoded[2]
        mantissa *= 256
        mantissa += encoded[1]
        mantissa *= 256
        mantissa += encoded[0]
    
        # Divide the number by the mantissa, corrected
        # by the 24 bits we just shifted while reconstructing it
        mantissa /= math.pow(2, 24 - exp)
    
        # Apply the sign to the whole value
        if sign:
            mantissa = -mantissa
    
        return mantissa
    

« (précédent) Page 13 / 26 (suivant) »

Tous les tags

3d (15), 6502 (10), 6809 (1), 8bits (1), Affichage (24), AgonLight (2), Altaïr (1), Amstrad CPC (1), Apple (1), Aquarius (2), ASM (30), Atari (1), Atari 800 (1), Atari ST (2), Automatisation (4), BASIC (31), BASIC-80 (4), C (3), Calculs (1), CDC (1), Clion (1), cmake (1), Commodore (1), Commodore PET (1), Compression (4), CPU (1), Debug (5), Dithering (2), Divers (1), EF9345 (1), Émulation (7), Famicom (7), Forth (13), Game Jam (1), Hector (3), Histoire (1), Hooks (4), Huffman (1), i8008 (1), Image (17), Jeu (15), Jeu Vidéo (4), Livre (1), Logo (2), LZ (1), Machine virtuelle (2), Magazine (1), MAME (1), Matra Alice (3), MDLC (7), Micral (2), Motorola (1), MSX (1), Musée (2), Nintendo Switch (1), Nombres (3), Optimisation (1), Outils (3), Pascaline (1), Peertube (1), PHC-25 (2), Photo (2), Programmation (15), Python (1), RLE (1), ROM (15), RPUfOS (6), Salon (1), SC-3000 (1), Schéma (5), Synthèse (15), Tortue (1), Triceraprog (1), VG5000 (62), VIC-20 (1), Vidéo (1), Z80 (21), z88dk (1), ZX0 (1)

Les derniers articles

Forth sur 6502, épisode 10
Forth sur 6502, épisode 9
Forth sur 6502, épisode 8
Forth sur 6502, épisode 7
Forth sur 6502, épisode 6
Forth sur 6502, épisode 5
Forth sur 6502, épisode 4
Forth sur 6502, épisode 3
Forth sur 6502, épisode 2
Forth sur 6502, épisode 1

Atom Feed

Réseaux