Formatage des nombres

Le formatage des nombres sous forme de chaines de caractères en vue de leur affichage est une fonctionnalité clé quand on développe des applications à vocations techniques ou scientifiques. Les languages comme le C++ disposent de beaucoup de possibilités pour fournir cette fonctionnalité. Et la librairie de Qt en rajoute encore une couche. Cela dit, lorsque l'on est un scientifique ou un ingénieur, on a ses petites habitudes de professionnel... et dans ce cas, il peut y avoir quelques limites pas toujours simples à régler avec ces fonctions de formatage de nombres. Deux exemples...


Un truc que l'on fait souvent dans une application est de simuler un afficheur numérique d'un instrument de mesure. Ces afficheurs ont de bonnes habitudes. Quelque soit la mesure qu'ils affichent, le nombre est toujours formatté de la même manière, le nombre de chiffres significatifs avant ou après le séparateur décimal ne change pas, le séparateur décimal ne bouge pas, le signe non plus, les exposants encore moins, etc... Cela rend la lecture de la mesure plus rapide et plus aisée. C'est un comportement assez difficile à obtenir avec les routines de formatage des nombres des languages informatiques. Comme ce formatage est essentiellment guidé par le chiffre lui même, on observe souvent des glissements de décades, etc... Bref, obtenir un affichage stable, et cela quelque soit le nombre affiché peut conduire à de sacrées difficultés de formatage avec notre bon vieux printf.


Mais la chose la plus frustrante est que ces fonctions de formatage ne respectent pas la norme ISO des décades. Un ingénieur ne dira jamais "J'ai mesuré 12e10-5 Ampère". Il dira "J'ai mesuré 120 µ Ampère". Cela correspond à 120e10-6. Quand on parle de mesures physiques, on ne parle que selon ces echelles de décades ISO, des multiples de 3 : Kilo, Méga, Giga, etc... et dans l'autre sens : milli, micro, nano, pico, etc... Cela correspond à 10e3, 10e6, 10e9... 10e-3, 10e-6, 10e-9, 10e-12. La norme ISO définie toutes ces décades de 10e-24 à 10e24. Et elles ont toutes un petit nom :


Exposant Nom Préfixe d'unité
-24Yoctoy
-21Zeptoz
-18Attoa
-15Femtof
-12Picop
-9Nanon
-6Microµ
-3Millim
0
3KiloK
6MegaM
9GigaG
12TeraT
15PetaP
18ExaE
21ZettaZ
24YottaY

Essayez donc de demander à votre language de formater vos nombres uniquement selon cette echelle... Quand à moi, je n'ai jamais trouvé de solution élégante.

Pour formater les nombres vous pouvez utiliser la classe TNumberCruncher ou le widget TNumber.

Comment ça marche ?

La classe expose 13 propriétés qui définissent le formatage :

Propriété Type Signification succincte
DigitsAfterDecimalSeparator int Nombres de chiffres après le séparateur décimal
DigitsBeforeDecimalSeparator int Nombres de chiffres avant le séparateur décimal
DigitsForIntegers int Nombres de chiffres significatifs pour les nombres entiers
EnableGroupSeparator bool Activation de la séparation par groupes de 3 chiffres
ForceCLocal bool Forçage du point comme séparateur décimal
ForceSign bool Forçage du signe
MessageOverflow QString Message de dépassement de gamme
NumberFormat TXS::NumberFormat Directive de formatage principale
PadCharacter QChar Caractère de remplissage
ShowXOB bool Affichage du préfixe pour les bases binaire, octodécimale, hexadécimale
TimeFormat TXS::TimeFormat Directive de formatage des dates et heures
UpperXOB bool Préfixe de base binaire, ocotdécimale, hexa décimale en majuscules
XOBFormat TXS::XOBFormat Formatage des entiers en base binaire, octodécimale, hexadécimale

Ces propriétés ont ou n'ont pas de significaiton selon le type de valeur à formater et selon la valeur attribuée à la propriété NumberFormat.

On distingue 3 cas possibles :

La valeur à formater est une date et/ou une heure :

Dans ce cas, la propriété NumberFormat prend la valeur TXS::NumberFormat_DateTime. Le format de date et/ou heure est alors uniquement défini par la propriété TimeFormat.

La valeur à formater est un nombre entier :

Le nombre peut être codé de 8 à 64 bit. La propriété NumberFormat peut alors prendre plusieurs valeurs. Le tableau suivant résume l'impact des autres propriétés sur le formatage en fonction de la valeur de la propriété NumberFormat :


Les quelques règles suivantes s'appliquent :

 - Les formats TXS::NumberFormat_Integer_AutoDecimal et TXS::NumberFormat_Integer_Decimal peuvent traiter des nombres signés ou non. Les autres formats cités dans le tableau considèrent que la valeur est un entier non signé.

 - Les formats automatiques définissent eux mêmes le nombre de chiffres significatifs.

 - Pour les autres formats non automatiques, le nombre de chiffres significatifs est spécifié via la propriété DigitsForIntegers. Si cette propriété ne perrmet pas de formater la valeur, la valeur de la propriété MessageOverflow est retournée. Si cette propriété définie plus de chiffres significatifs que nécessaire, le caractère définie par la propriété PadCharacter est ajouté en début de chaine jusqu'à obtenir le nombre de caractères requis.

 - Si la valeur à formater n'est pas un entier mais un réel, elle est d'abord "castée" au type qlonglong.

 - La fonctionnalité de séparation de groupe via la propriété EnableGroupSeparator ne s'applique qu'au format décimal. Les formats hexadécimal, octodécimal et binaire ont une séparation par groupes de bit via la propriété XOBFormat.

 - Lorsque la propriété ForceCLocal est placée à la valeur true, la propriété EnableGroupSeparator n'a plus d'influence car le format des nombres en C ne comporte jamais de séparations de groupes.

 - Le préfixe pour la base héxadécimale est "0x". Le préfixe pour la base octodécimale est "0o". Le préfixe pour la base binaire est "0b".

 - Lors de l'utilisation des formats TXS::NumberFormat_Integer_HexaDecimal, TXS::NumberFormat_Integer_OctoDecimal et TXS::NumberFormat_Integer_Binary, il faut faire attention au fait que pour respecter la notaation par groupe de nibbles, octets ou mots, la librairie peut rajouter des zéros en tête de la valeur produite. Cela peut conduire à un dépassement du nombre de chiffres significatifs précisé par la propriété DigitsForIntegers et donc retourner une valeur hors gamme telle que définie par la propriété MessageOverflow. Il est donc nécessaire de donner à la propriété DigitsForIntegers une valeur adéquate en fonction du format en nibbles, octets ou mots.

Exemples :

Les exemples suivants sont donnés par formats. La valeur à convertir est la même pour tous les formats : +123456789. Dans tous ces exemples, la propriété PadCharacter est réglée à '_', la propriété MessageOverflow est réglée à "OVL".

  Format TXS::NumberFormat_Integer_AutoDecimal

ForceSign EnableGroupSeparator ForceCLocal Résultat
true true false +123 456 789
true false false +123456789
false true false 123 456 789
false true true 123456789

  Format TXS::NumberFormat_Integer_Decimal

DigitsForIntegers ForceSign EnableGroupSeparator ForceCLocal Résultat
7 true true false OVL
9 true false false +123456789
12 true false false +___123456789
12 false true false ___123 456 789
12 false true true ___123456789

  Format TXS::NumberFormat_Integer_AutoHexaDecimal

XBOFormat ShowXOB UpperXOB Résultat
XOBFormat_None false false 75bcd15
XOBFormat_Nibble false false 7 5 b c d 1 5
XOBFormat_Byte true false 0x07 5b cd 15
XOBFormat_Word true true 0X075B CD15

  Format TXS::NumberFormat_Integer_HexaDecimal

DigitsForIntegers XBOFormat ShowXOB UpperXOB Résultat
9 XOBFormat_None true false 0x__75bcd15
5 XOBFormat_None true false OVL
9 XOBFormat_Nibble true false 0x__7 5 b c d 1 5
9 XOBFormat_Word true true 0X_075B CD15
8 XOBFormat_Word true true 0X075B CD15
7 XOBFormat_Word true true OVL

  Format TXS::NumberFormat_Integer_AutoOctoDecimal

XBOFormat ShowXOB UpperXOB Résultat
XOBFormat_None false false 726746425
XOBFormat_Nibble false false 07 26 74 64 25
XOBFormat_Byte true false 0o726 746 425
XOBFormat_Word true true 0O000726 746425

  Format TXS::NumberFormat_Integer_OctoDecimal

DigitsForIntegers XBOFormat ShowXOB UpperXOB Résultat
9 XOBFormat_None true false 0o726746425
5 XOBFormat_None true false OVL
9 XOBFormat_Nibble true false OVL
9 XOBFormat_Byte true false 0o726 746 425
9 XOBFormat_Word true true OVL
16 XOBFormat_Word true true 0O____000726 746425

  Format TXS::NumberFormat_Integer_AutoBinary

XBOFormat ShowXOB UpperXOB Résultat
XOBFormat_None false false 111010110111100110100010101
XOBFormat_Nibble false false 0111 0101 1011 1100 1101 0001 0101
XOBFormat_Byte true false 0b00000111 01011011 11001101 00010101
XOBFormat_Word true true 0B0000011101011011 1100110100010101

  Format TXS::NumberFormat_Integer_Binary

Digits... XBOFormat ShowXOB UpperXOB Résultat
27 XOBFormat_None false false 111010110111100110100010101
24 XOBFormat_None false false OVL
27 XOBFormat_Nibble false false OVL
28 XOBFormat_Nibble false false 0111 0101 1011 1100 1101 0001 0101
27 XOBFormat_Word true false OVL
32 XOBFormat_Word false false 0000011101011011 1100110100010101
35 XOBFormat_Word false false ___0000011101011011 1100110100010101

La valeur à formater est un nombre réel :

Le nombre peut être du type float ou du type double. La propriété NumberFormat peut alors prendre plusieurs valeurs. Le tableau suivant résume l'impact des autres propriétés sur le formatage en fonction de la valeur de la propriété NumberFormat :


Les quelques règles suivantes s'appliquent :

 - Le format TXS::NumberFormat_Any_Compact garanti la conversion en chaine de caractères. Les propriétés PadCharacter et MessageOverflow n'ont pas d'influence. Ce format retourne la chaine la plus courte possible quelque soit son format numérique. Ainsi vous ne pouvez pas être assuré que la chaine retournée sera au format à virgule flottante ou au format scientifique avec exposant.

 - Les formats TXS::NumberFormat_Double_AutoFixedPoint et TXS::NumberFormat_Double_FixedPoint sont équivalents. La seule différence est que le format TXS::NumberFormat_Double_AutoFixedPoint calcule lui même le nombre de chiffres significatifs avant la virgule pour éviter tout caractères de complément ou le message de dépassement de gamme définis par les propriétés PadCharacter et MessageOverflow. Le revers de la médaille du mode TXS::NumberFormat_Double_AutoFixedPoint est que vous ne pouvez pas être sur de toujours afficher le même nombre de chiffres significatifs avant le séparateur décimal. Généralement, cela ne gêne pas une interprétation simple car le séparateur décimal ne change jamais de position. Dans ces deux formats, si un dépassement de gamme est détecté, le contenu de la propriété MessageOverflow est retournée. Si le nombre de chiffres spécifié avant le séparateur décimal via la propriété DigitsBeforeDecimalSeparator est supérieur au nombre nécessaire le nombre sera précédé du caractère défini pour la propriété PadCharacter. Enfin, si le nombre de chiffres après le séparateur décimal spécifié par la propriété DigitsAfterDecimalSeparator est supérieur au nombre nécessaire, les derniers chiffres seront des zéros.

 - Le format TXS::NumberFormat_Double_Scientific fonctionne de manière strictement identique aux formats TXS::NumberFormat_Double_AutoFixedPoint et TXS::NumberFormat_Double_FixedPoint mais il garantit une chaine de caractères retournée sous forme de valeur avec exposant.

 - Les formats TXS::NumberFormat_Double_ScientificStandard et TXS::NumberFormat_Double_ScientificRange garantissent une chaine de caractères retournée sous forme de valeur avec exposant. Contrairement au format TXS::NumberFormat_Double_Scientific ces deux formats garantissent de ne retourner que des exposants multiples de 3 respectant ainsi la norme ISO. Le format TXS::NumberFormat_Double_ScientificRange remplace l'exposant par le préfixe d'unité. Pour ces formats la propriété DigitsBeforeDecimalSeparator ne constitue pas une obligation, mais un minimum de chiffres significatifs avant le séparateur décimal. Cela du fait que pour aligner l'exposant sur un multiple de 3, il est possible que le séparateur décimal doivent être déplacé afin de conserver la résolution demandée.

 - Lorsque la propriété ForceCLocal est placée à la valeur true, la propriété EnableGroupSeparator n'a plus d'influence car le format des nombres en C ne comporte jamais de séparations de groupes.

Exemples :

  Format TXS::NumberFormat_Any_Compact (Valeur test = 1234,56789)

ForceSign EnableGroupSeparator ForceCLocal Résultat
false true false 1 234,57
true true false +1 234,57
false true true 1234,57

  Format TXS::NumberFormat_Double_AutoFixedPoint (Valeur test = 1234,56789)

DigitsAfter.. ForceSign EnableGroupSeparator ForceCLocal Résultat
9 false false true 1234.567890000
6 true true false +1 234,567890

  Format TXS::NumberFormat_Double_FixedPoint (Valeur test = 123,456789)

DigitsBefore.. DigitsAfter.. ForceSign EnableGroupSeparator ForceCLocal Résultat
9 6 false false true _____1234.567890
6 6 true true false +__1 234,567890
2 6 true true false OVL

  Format TXS::NumberFormat_Double_Scientific (Valeur test = 0,000000123456789)

DigitsBefore.. DigitsAfter.. ForceSign EnableGroupSeparator ForceCLocal Résultat
4 6 false false false ___1,234568e-07
2 6 false false true _1.234568e-07
4 3 false false false ___1,235-07
4 3 true false false +___1,235-07
1 5 true false false +1,23457-07

  Format TXS::NumberFormat_Double_ScientificStandard (Valeur test = 0,000000123456789)

DigitsBefore.. DigitsAfter.. ForceSign EnableGroupSeparator ForceCLocal Résultat
1 5 true false false +123,45700e-9
3 8 false false false 123,45678900e-9

  Format TXS::NumberFormat_Double_ScientificStandard (Valeur test = 0,000000123456789)

DigitsBefore.. DigitsAfter.. ForceSign EnableGroupSeparator ForceCLocal Résultat
1 8 true false false +123,456789000 n
3 8 false false false 123,45678900 n