Représentation des flottants : détournons la norme IEEE 754 pour y comprendre quelque-chose

Représentation scientifique des nombres

En base 10

Tout nombre non-nul peut être représenté sous la forme d’une représentation scientifique, c’est-à-dire :

42 = 4,2 \times 10^1

La décomposition se fait entre mantisse (partie de gauche) et exposant sur le 10 :

  • la mantisse doit être comprise entre 1 inclus et 10 exclu, c’est-à-dire que la partie entière de la mantisse doit être constituée d’un unique chiffre non-nul ;
  • l’exposant doit être un entier relatif.

En base 2

En reprenant cette idée de représentation scientifique, on obtient, pour la base 2 :

42_{10} = 00101010_{2} = 1.01010 \times 2^5

Remarquez que le chiffre à gauche de la virgule étant non-nul, il s’agit forcément de 1 en base 2. Donc, pour stocker en mémoire n’importe quel nombre réel non-nul, il suffit de stocker la partie droite de la mantisse (le 1 étant toujours présent, il n’est pas utile de le stocker) et la puissance.

Le problème, bien sûr, est que la mantisse peut-être a priori infini, et que les exposants peuvent être très grand. Pour l’exposant, ce n’est pas forcément un très grand soucis, quelques petites astuces pouvant être utilisées pour représenter des nombres d’une taille arbitrairement grand (c’est le cas pour les entiers int en Python 3).

Les astuces pour la mantisse sont plus compliquées : au lieu de la constitution de la mantisse, il faut plutôt stocker l’algorithme permettant de calculer une mantisse de taille arbitraire. Si dans certains cas, des calculs à précision arbitraire (on parle d’arithmétique multi-précision) peuvent être intéressants, dans l’immense majorité des cas (la quasi-totalité en fait), des calculs à précision constante sont suffisants pour les calculs effectués.

L’une des principales raisons étant le principe garbage in, garbage out : il n’est pas utile de travailler sur beaucoup plus de chiffres significatifs que les chiffres significatifs des mesures : cela pourrait même pousser les utilisateurs les moins prudents à sur-estimer la précision de leur calcul.

Les représentations binaires IEEE 754 standard

Le principe de l’arithmétique de la norme IEEE 754 (prononcé I3E 754) est de tronqué la mantisse et de limiter les exposants. Pour ne pas avoir à se soucier de stocker des exposants négatifs, un décalage est présent. On a donc le format suivant pour la quasi-totalité des nombres de la norme :

(-1)^{\varepsilon} \times 1,m \times 2^{e-d}

1 bit est toujours utilisé pour le signe (0 pour les positifs, 1 pour les négatifs). Le partage du reste des bits entre mantisse et exposant est synthétisé dans le tableau suivant :

CodeNomBits (octets)Taille exposantTaille mantisse
binary16Demi-précision16 (2)510
binary32 Simple précision32 (4)823
binary64 Précision double64 (8)1152
binary128 Précision quadruple128 (16)15112
binary256Précision octuple 256 (32)19236
Constitution des flottants suivant les normes binaires IEEE 754

On voit que la taille de l’exposant a tendance à grandir beaucoup plus lentement que la mantisse. En effet, les utilisations de nombres très grands ou très petits est souvent rare, et on peut facilement se ramené à des nombres proches de l’unité en faisant des choix pertinents d’unités et de sous-unités si le besoin se fait ressentir. Par contre, l’augmentation de la taille de la mantisse est primordial pour augmenter la précision relative des nombres (plus de chiffres significatifs).

Le décalage de l’exposant dépend de la taille en bits de l’exposant :

d = 2^{te-1} - 1

Subtilités de la norme IEEE 754 : 0, nombres dénormalisés, infini et les non-nombres

2 valeurs particulières d’exposant entraine une interprétation différentes :

  • Lorsque l’exposant est à 0, on se retrouve avec une exposant qui correspond à celui de 1, mais une mantisse qui commence par 0 à gauche de la virgule. Cela permet en particulier de coder le 0 lorsque la mantisse est à 0. On parle de nombres dénormalisés pour cet ensemble de nombres (0 étant exclu). Cette astuce permet de coder le 0 et d’avoir une certaine continuité plus ou moins factice entre 0 et le premier nombre normalisé.
  • Lorsque l’exposant est maximum et la mantisse nul, cela représente l’infini inf.
  • Lorsque l’exposant est maximum et la mantisse est non-nulle, cela représente un non-nombre : NaN, Not a Number. Si la mantisse commence par 0, cela représente un NaN avertisseur (signaling) et si elle commence par 1, cela représente un NaN silencieux (quiet). Le premier doit provoquer une erreur lors de calcul, tandis que le deuxième doit juste s’étendre : le résultat d’un calcul avec un NaN retournant un NaN (le bit précis signaliant la différence entre les deux NaN est une spécificité de la norme IEEE 754-2008).

Il est à noté que l’implémentation totale et entière de la norme n’est pas toujours respecté. Python, par exemple, retourne une erreur au lieu de l’infini lors d’une division par 0, contrairement à la recommandation de la norme.

Formule donnant la taille de l’exposant en fonction de la taille du nombre

On a des normes pour des nombres d’octets allant de 2 à 32 en doublant à chaque fois la taille. C’est assez logique en considérant les architectures des ordinateurs. Mais pour un but pédagogique, ou pour des applications très spécifiques, on peut s’inspirer de la norme IEEE-754 pour arriver à une nouvelle norme cohérente (comme les fomats minifloat).

Une formule permettant de donner la taille de l’exposant en bits te en fonction de la taille du nombre en bit tn est la suivante :

te = 0,356 \times \log_2 (tn)^{1,919}

Cela donne 3 bits d’exposant pour un nombre de 8 bits (1 octet). Cela permet d’avoir une mantisse de 4 bits (une moitié d’octets), ce qui est plutôt intéressant (il existe des possibilités de travailler plus ou moins indépendamment sur les 2 moitiés d’un octet sur beaucoup d’architectures matérielles).

Flottant quart de précision (sur 1 octet)

Attention, tout ce qui va être discuté ensuite n’a qu’une visée pédagogique : cela sera le plus possible cohérent avec le reste de la norme, mais il ne s’agit ni d’un nom, ni d’une représentation officielle des flottants suivant la norme IEEE 754 (à ma connaissance). D’après mes recherches, elle semble être utilisée dans certains contextes (en particulier dans des situations d’informatique embarquée), mais ce n’est pas ce qui m’intéresse particulièrement dans ce billet. L’intérêt est de pouvoir représenter la totalité des nombres de la norme dans un tableau !

Décalage d’exposant

On a un exposant de taille 3, donc un décalage de 2^{3-1} - 1 = 3. Les exposants ont des valeurs de 0 à 7, mais 0 représente les nombres dénormalisés et 7 représente l’infini et les NaN.

Nombres normalisés

Les nombres normalisés ont donc un exposant non-décalé de 1 à 6, et donc, avec le décalage de 3, les exposants sont compris entre -2 et 3. Le plus petit nombre normalisé est 1,000_2 \times 2^{-2}  = 0,25_{10}. Le plus grand nombre est 1,1111_2 \times 2^3 = 15,5_{10}.

Nombres dénormalisés

Les nombres dénormalisés ont un exposant de -2. Le plus petit nombre dénormalisé strictement positif est donc : 0,0001_2 \times 2^{-2}  = 0,015625_{10} et le plus grand nombre dénormalisé est 0,1111_2 \times 2^{-2} = 0,234375 _{10} .

Tableaux des 128 valeurs positives du format quart de précision

000001010011100101110111
00000.00.250.51.02.04.08.0inf
00010,0156250,2656250,531251,06252,1254,258,5sNaN
00100,031250,281250,56251,1252,254,59 sNaN
00110,0468750,2968750,593751,18752,3754,759,5 sNaN
01000,06250,31250,6251,252,5510 sNaN
01010,0781250,3281250,656251,31252,6255,2510,5 sNaN
01100,093750,343750,68751,3752,755,511 sNaN
01110,1093750,3593750,718751,43752,8755,7511,5 sNaN
10000,1250,3750,751,53612qNaN
10010,1406250,3906250,781251,56253,1256,2512,5qNaN
10100,156250,406250,81251,6253,256,513qNaN
10110,1718750,4218750,843751,68753,3756,7513,5qNaN
11000,18750,43750,8751,753,5714qNaN
11010,2031250,4531250,906251,81253,6257,2514,5qNaN
11100,218750,468750,93751,8753,757,515qNaN
11110,2343750,4843750,968751,93753,8757,7515,5qNaN
Tri des 128 valeurs décimales du format quart de précision (les 16 lignes représentent les mantisses et les 8 colonnes les exposants)

Comparaison avec les flottants de Python

Pour connaitre les performances de votre systèmes concernant les flottants, vous pouvez utiliser le module sys :

>>> import sys ; sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, 
min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, 
mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)

Sauf si vous avez une architecture vraiment primitive, vous devriez avoir un résultat très similaire à celui-ci :

  • radix =2 confirme qu’il s’agit d’un format binaire (la norme IEEE 754 permet des représentation en base 10, ce qui n’est pas discuté dans ce billet, car très rarement utilisé et moins efficace).
  • max et min représentent les extremas des valeurs normalisées. On a 15,5 et 0,25 pour le format quart de précision.
  • max_exp et min_exp avec un décalage de 1 (précisé dans la documentation, mais j’avoue que je suis surpris de ce choix…), donnent les valeurs des extremas possible pour les puissances de radix : ici 2^{1023} et 2^{-1022} sont représentables par des valeurs normalisés. On a 4 et -1 pour le format quart de précision (car 2^3 et 2^{-2} sont représentables par des valeurs normalisés).
  • max_10_exp et min_10_exp correspondent aux puissances de 10 extrêmes des valeurs normalisées (sans décalage cette fois). On a 1 et -1 pour le format quart de précision.
  • mant_dig correspond au nombre de chiffres significatif en base 2 pour les nombres normalisés (donc taille de la mantisse plus 1). On a 5 pour le format quart de précision.
  • dig correspond au nombre de chiffre significatif en décimal. On a 1 pour le format quart de précision
  • epsilon correspond à la différence entre le nombre 1 et son successeur : en pratique, c’est une borne haute de la précision relative entre 2 successeurs, et donc, en général de la précision relative des calculs. On a 0,0625 pour le format quart de précision
  • round correspond au mode d’arrondi : il utilise l’arrondi au plus proche du nombre pair, ce qui est la méthode classique en informatique (j’avoue ne pas savoir pourquoi…).

Représentation graphique de la répartition et de l’erreur relative

Répartitions des nombres flottants au format quart de précision
Écart relatif du successeur des nombres flottants au format quart de précision

Si on fait la même chose pour le format demi-précision, on y voit tout de suite un peu moins clair et des subtilités visibles sur un format de précision moindre passe à la trappe.

Répartitions des nombres flottants au format demi-précision
Écart relatif du successeur des nombres flottants au format demi-précision

Toolbox4SII : un projet qui me tient à cœur

Voilà, la suite du projet annoncé est en cours… Il a maintenant un nom, un site web, une petite communauté, des demandes de soutien…

La plupart de mes productions intellectuelles ces prochaines semaines seront là pour alimenter ce site web, pour finaliser les machines virtuelles en cours de conception et pour préparer les collaborations de l’année prochaine avec mes collègues.

Malheureusement peu de temps à consacrer à ce blog, mais une petite refonte suite à quelques trouvailles faites sur les possibilités de WordPress. Je pense que mon contenu pourrait être avantageusement présenté sous la forme d’un wiki… Mais il faut que je regarde encore un peu comment organiser ça.

Développement d’un OS pour les S(T/I/.)I

Histoire du projet

Suite à des discussions avec des collègues, on s’est retrouvé embêtés pendant le confinement pour que les étudiants aient accès à tous les outils numériques nécessaires, en particulier Scilab version 5.5.2 qui est très utilisé et qui pose quelques soucis sur les dernières versions de mac et linux.

Après plusieurs tâtonnement, j’ai réussi à proposer une machine virtuelle Lubuntu 20.04 (disponible au téléchargement) avec Scilab 5.5.2 de pré-installé [première VM envoyé par lien le 10 mai, version actuelle le 12 mai]. Documents de prise en main :

Très vite, on m’a informé que cette solution était bien puissante, durable et pertinente qu’il n’y paraissait au premier abord. Intéressé par le libre (sans encore être un vrai militant), j’ai tout de suite pensé que pour que l’outil soit pertinent, viable et qu’on trouve toujours des personnes pour le maintenir dans le temps, même dans 5 ou 10 ans, il fallait qu’il soit collaboratif, donc j’ai beaucoup partagé sur la mailing liste de mon association de professeur (que je remercie beaucoup de m’avoir supporté, merci et pardon à l’UPSTI et à ses adhérents pour le flood) pour constituer un groupe de travail.

Très vite s’est posé la question de la vision de l’historique du projet par les membres, du fait que je centralisais beaucoup d’information de beaucoup de part, avec beaucoup de redondance dans les apports. J’ai alors fait la semi-erreur de lancer un serveur Discord pour améliorer la visibilité pour ceux qui étaient intéressés, puis quelques jours après, l’UPSTI a proposé ce même type de solution d’échange pour la communication entre ses membres et m’a invité à les rejoindre. J’ai fait la semi-erreur d’accepter la proposition. Je parle de semi-erreurs car ces étapes ont quand même permis de beaucoup faire avancer le projet, mais elles ont le problème de parasiter la communication : une association qui crée son serveur d’échange n’a pas les mêmes problématiques qu’un projet informatique, et je me suis rendu compte que les échanges qui étaient foisonnants ont été beaucoup plus limités, le publique étant plus large que juste les intéressés, les discussions plus large que juste l’accomplissement du projet… Tout en ne pouvant pas inclure les non-membres de l’UPSTI, voir même des étudiants (qui seront les premiers utilisateurs avec nous de cette future solution).

Les échanges ont été aussi parasités (plutôt positivement, des solutions pertinentes ont été proposées et sont à discuter) par la question des choix des moyens de communication (Discord, Riot,…). Et je pense que c’est aux personnes intéressées de faire ce choix de façon démocratique au sein du projet dont je suis le porteur.

Cette façon d’approcher un projet n’étant pas classique, la grande majorité de mes collègues ayant d’autres priorités, je me suis aussi rendu compte qu’il fallait limiter l’échange vers les collègues les moins intéressés par le projet avec uniquement des propositions fonctionnelles. Les personnes extérieures aux projets ne doivent que voir un outil pertinent à la fin du projet, pas toutes les élucubrations nécessaires pour arriver à cette solution, sinon, on parasite l’outil proposé et sa publicité.

Présent du projet [24 mai 2020]

On reprend tout à 0 au niveau de la communication, et on est ouvert à tous les futurs utilisateurs : 3 mailing listes concentriques : développement, test et curieux :

https://framalistes.org/sympa/subscribe/os4upsti_curieux
https://framalistes.org/sympa/subscribe/os4upsti_testeurs

Les testeurs devront être prêt à être solliciter activement par les développeurs qui demanderont des retours réguliers, je préviens juste. Les curieux n’ont pas le droit d’être déçu des propositions. Elles seront forcément imparfaites jusqu’à la fin du développement (et même à la fin du développement, hein… rien n’est parfait). N’oubliez pas de vous inscrire sur les deux si vous souhaitez être testeur.

Les premières questions qui se poseront et qui seront à résoudre :

  • Quelle communication souhaite-t-on vers l’extérieur du projet ?
  • Quels outils veux-t-on pour améliorer les échanges à l’intérieur du projet ?
  • Quelles sont les priorités du développement pour la rentrée ?

Mots d’ordres

Démocratique dans ses choix collectifs, ouvert dans son développement, attentif dans sa communication extérieur.

Futur du projet

Ce sera à voir. En tant que porteur, j’ai déjà en tête un plan d’action technique pour arriver à une solution fonctionnelle pour la rentrée, mais je ne souhaite pas forcément briser des visions concurrentes si tôt dans le développement.

A priori, le développement se fera sur base d’un git publique dont l’adresse sera partagé sur ce blog.