Aller au contenu
Accueil » Nombres au format personnalisé en Python

Nombres au format personnalisé en Python

formatter les nombres en python

Bonjour à tous,
Aujourd’hui je voulais revenir sur un problème que j’ai eu il y a quelques années. Nous travaillions sur des modèles numériques et ces modèles sont écrits dans des fichiers au format texte. Pour éviter de trop les manipuler, il est préférable de modifier le modèle directement dans le fichier plutôt que de passer par un logiciel, surtout si la modification est mineure. Nous avons donc développer des outils qui scanne le modèle et qui remplace les valeurs au bon endroit. Cependant, les valeurs sont écrites sur 8 ou 16 digits et l’ordre de grandeur peut varier de 10-6 à 106. Nous avions donc besoin d’une fonction qui permette d’écrire les nombres réels dans un format personnalisé avec Python en conservant la plus grande précision possible.

Vous allez me dire qu’il y a des fonctions en Python qui font ça très bien. Effectivement, vous avez raison et j’en parle dans cet article sur la fonction « print ». Cependant, il y a tout de même plusieurs cas à gérer entre les nombres très grands et ceux proche de zéro, positifs et négatifs, … Il faut donc gérer tout ce petit monde et orienter le programme pour qu’il prenne la bonne décision. Pas si évident que ça finalement sachant qu’il faut mixer entre une écriture de la valeur arrondie ou au format scientifique.

A l’époque, nous n’avions pas ChatGPT. Mais aujourd’hui c’est différent, essayons de faire l’exercice. Ensuite, nous comparerons à la solution que nous avions développé.

Définition du problème

Tout d’abord, l’objectif de la fonction est de renvoyé un nombre réel formatté sur 8 ou 16 digits. Le résultat sera sous la forme d’une châine de caractère.

Quelque soit le choix de la forme de l’écriture du nombre, il faut que la plus grande précision soit conservée.

Ensuite, si la notation scientifique est utilisée, il faudra faire attention à ne pas faire apparaître de le E et supprimer les 0 inutiles de la puissance. En outre, il faut conserver signe et la valeur de la puissance pour condenser l’écriture. Voici un exemple sur 8 digits : 1.2345+6 ou -1.234-5.

Il faut ensuite définir la liste des tests à réaliser pour vérifier le fonctionnement de la fonction. Voici la liste utilisée.

liste = [0., 0.993456789,-0.1, 1.,-9.187654, 10., 1.0000765, 0.000000655, 1234., -1.120021, 
         123456789, -123456789]
Python

Programme développé avec ChatGPT

J’ai donc demandé à ChatGPT de me créer un script Python permettant de réaliser l’objectif décrit dans la section précédente. La première version proposée de prenait pas le format comme argument. Je lui ai donc demandé de réécrire la fonction dans ce sens. Voici le résultat :

def format_real2(number, digits=8):
    # Format the number with the specified number of digits
    format_str = f"{{:>{digits}g}}"
    formatted = format_str.format(number)

    # Determine if scientific notation is needed
    if "e" in formatted:
        mantissa, exponent = formatted.split("e")
        if int(exponent) >= 0:
            sign = "+"
        else:
            sign = "-"
        exponent = abs(int(exponent))
        mantissa = float(mantissa)
        # Remove trailing zeros from the mantissa
        mantissa_str = str(mantissa).rstrip("0").rstrip(".")
        return f"{mantissa_str}{sign}{exponent}"
    else:
        return formatted.strip()
Python

La fonctionnalité f-strings est utilisée ici. Si vous voulez plus de détails, j’en parle dans mon article sur la fonction « print ». La clé de conversion « g » permet de formatter un nombre de façon générale. En fonction de la précision sur le formattage, le choix de l’écriture décimale ou scientifique est réalisé automatiquement. Le formattage se fait sur deux lignes alors qu’on peut le faire aisément sur une seule mais pourquoi pas.

La gestion de la forme scientifique est intéressante et plutôt compacte. Il y a des choses à récupérer dans ce qui a été fait. Cependant, le programme ne prend pas en compte le fait que s’il y a des zéros en moins sur l’exposant, cela change le nombre de digits disponible dans la mantisse. Le gros point noir dans ce programme est qu’il ne calcule pas le nombre de digits disponible pour la partie décimale ou la mantisse. Voyons voir le résultat :

Nombre = 0            digits = 1
Nombre = 0.993457     digits = 8
Nombre = -0.1         digits = 4
Nombre = 1            digits = 1
Nombre = -9.18765     digits = 8
Nombre = 10           digits = 2
Nombre = 1.00008      digits = 7
Nombre = 6.55-7       digits = 6
Nombre = 1234         digits = 4
Nombre = -1.12002     digits = 8
Nombre = 1.23457+8    digits = 9
Nombre = -1.23457+8   digits = 10
Python

Le programme fonctionne sur quelques cas mais il reste encore des cas non traités comme les nombres réels qui se transforment en nombre entier ou la bonne gestion de l’écriture scientifique pour avoir 8 digits.

Le programme existant pour formatter nos nombres

La fonction va décider suivant la valeur étudiée si l’écriture sera réalisée en décimale ou en scientifique. Le programme n’est pas parfait. Il a été réalisé avec les quelques connaissances en programmation de l’époque donc tout n’est pas écrit en suivant les méthodes décrites dans cet article. Le résultat de ChatGPT n’est pas parfait mais je pense qu’en combinant ce qui a été fait et ce qu’a donné ChatGPT, le résultat sera très intéressant. En attendant, voyons le programme existant.

def nas_float_format(str,format=8):
# The aim if this function is to create a compatible string for nastran with a float as input
# A float is expected as an input
    from math import log10
    
    try:
        X = float(str)
    except ValueError:
        X = GetFloat(str)
    except AttributeError:
        X = GetFloat(str)
    except TypeError:
        X = GetFloat(str)
    intX = int(X)
	
    # handle the 0. value and also the -0.0 problem
    if X == 0:
        X = int(X)
        nb_d = format - 2
    # evaluation of the number of digits available
    # 0.1 is an acceptable threshold to switch from decimal to scientific
    elif X < -0.1:
        nb_d = format - int(log10(abs(intX) + 0.11)) - 3
    elif X > 0.1:
        nb_d = format - int(log10(abs(intX) + 0.11)) - 2
    else : 
        nb_d = -1
	
    if nb_d > 0:
        string = '{0:.{width}f}'.format(X, width=nb_d)
    else:
        string = simplified_scientific_writing(X,format)
	
    return string
Python

On remarque une entête pour présenter la fonction. Ensuite, la fonction GetFloat a été créée pour palier à l’écriture possible de certaines valeurs. En effet, certains programmes écrivent dans les fichiers avec le format suivant : 1.2345+6 ce qui n’est bien entendu pas compréhensible par le fonction « float » de Python.

Le premier cas traité est celui de la valeur nulle. Lorsqu’il est rencontré, on utilise la fonction « int » pour éviter une écriture du type « -0.0 » qu’il est possible de rencontrer. On fixe ensuite le nombre de digits au nombre total minoré de deux car le zéro et le point prennent deux digits.

Ensuite, on considère que si la valeur est supérieure à 0.1 ou inférieure à -0.1, il faut estimer le nombre de digits disponible pour les décimales. C’est l’objet de la formule avec le logarithme en base 10. Si la valeur est positive, cela veut dire qu’il y a de la place pour les décimales, on utilisera alors cette valeur pour formatter la valeur. Si ce n’est pas le cas, on utilisera alors la notation scientifique dont voici la fonction :

def simplified_scientific_writing(value,format):
    power = 0
    sgn = value/abs(value)
    nb = value
    if abs(value) > 1:
        sign = "+"
        while abs(nb) > 10:
            power += 1
            nb = value / (10**power)
    else:
        sign="-"
        while abs(nb) < 1:
            power += 1
            nb = value*10**power
	
    #Length used by the power
    m = len(str(power))
    # number of digits available
    # -#.+#
    s = (sgn-1)*0.5
    n = format + s - m - 3
    
    nb = int(nb*10**n+sgn*0.5)/(10**n)
    X = str(nb)
    P = str(power)
    val_str= X + sign + P	
		
    if len(val_str) < format:
        s = format - len(val_str)
        for i in range(s):
            val_str += " "
    return val_str
Python

Le but de la fonction est d’écrire la mantisse (la partie nombre réel de la notation scientifique) qui doit être comprise entre 1 inclus et 10 exclus, soit l’intervalle [1 ; 10[. La boucle « while » permet de multiplier ou diviser le nombre afin d’obtenir un résultat compris dans l’intervalle cité précédemment. Cela dit en passant, nous aurions pu utiliser à nouveau la fonction logarithme en base 10, cela aurait été plus élégant. A la suite de cette boucle, nous avons donc la mantisse et la puissance associée. La suite de la fonction consiste à construire la chaîne de caractères et rajouter des espaces si le format final ne respecte pas le nombre de digits désiré. Voici le résultat pour les mêmes tests que précédemment.

Nombre = 0.000000     digits = 8
Nombre = 0.993457     digits = 8
Nombre = -1.0-1       digits = 8
Nombre = 1.000000     digits = 8
Nombre = -9.18765     digits = 8
Nombre = 10.00000     digits = 8
Nombre = 1.000077     digits = 8
Nombre = 6.55-7       digits = 8
Nombre = 1234.000     digits = 8
Nombre = -1.12002     digits = 8
Nombre = 1.2346+8     digits = 8
Nombre = -1.235+8     digits = 8
Python

Conclusion

Fort heureusement, le programme existant respecte le cahier des charges. On pourrait améliorer encore programme en supprimant le 0 des nombres écrits en 0.XXXXXX. En effet, les logiciels de calculs de structures comprennent qu’il y a un zéro devant une chaîne de caractères de ce type : « .XXXXXX ». Cela peut faire gagner encore un digit de précision.

Concernant l’écriture du code, il y a certains blocs qui peuvent être mieux condensés. Le format « f-strings » n’était pas encore disponible lorsque le programme a été écrit, il pourrait être mis à jour dans ce sens. La PEP8 n’est pas du tout respectée ici. Un effort sera à faire de ce côté là.

Clairement, ChatGPT nous fait gagner du temps car il nous mâche le gros du travail. Derrière il faudra reprendre le code pour ajuster si besoin. Je pense que cet outil est formidable pour aller plus sur les tâches à non-valeur ajoutée comme l’écriture du programme. Comme évoqué dans mon article sur ChatGPT, cet outil peut donc être vu comme un stagiaire qui aura fait 80% du travail (ce qui est pas mal du tout cela dit en passant). Au final, cela ne reste que des statistiques, il n’y a pas vraiment de réflexion derrière. A vous d’ajouter la couche d’intelligence pour maîtriser ce que réalise le programme.

Utiliser ChatGPT vous oblige à bien définir votre problème en amont. Ceci est une très bonne chose car c’est une étape importante et obligatoire qui passe souvent à la trappe. Cela vous entraînera à bien définir le cahier des charges d’une fonction et surtout bien définir les cas tests. Les cas tests sont conditionnés par votre compréhension du problème, votre faculté d’abstraction et de recul par rapport à ce qu’il peut se passer et également en fonction de votre expérience. De mon point de vue, il faut réaliser cet exercice avec plusieurs personnes qui vous donneront leurs différents points de vue.

Si vous avez aimé l'article, vous êtes libres de le partager ! :)

Laisser un commentaire