• [^] # Re: Il faut bien lire ce qu'on lit!

    Posté par (site web personnel) . En réponse au journal Le retour de la vengeance de la virgule flottante. Évalué à 4.

    Intéressant Xavier ! Je la connaissais pas cette politique d'arrondi pour les caisses électroniques :

    Ainsi, si le montant total du ticket de caisse se termine par 0,01 ou 0,02, il sera arrondi à 0 centime. Si le montant se termine par 0,03 ou 0,04 ou 0,05 ou 0,06 ou 0,07, il sera arrondi à 0,5 centimes. Enfin, le montant se terminant par 0,08 ou 0,09 sera arrondi à 0,10 centimes.

    Ce passage concernant la gestion des arrondis par la machine de caisse confirme bien qu'il n'existe aucune API toute faite permettant de pratiquer la politique d'arrondi et que le programmeur ne peut se dispenser de coder lui même l'arrondi.

    Une API de haut niveau pour gérer ce problème consiste à faire un type générique avec pour paramètres un type delta (virgule fixe) ET la fonction d'arrondi ad hoc.
    Exemple:

    -- le fichier de spécifications
    generic type Decimal is delta <>; -- on fournira, à l'instantiation du package, la précision et la plage
    with function my_round (float_value : Long_Float) return Decimal; -- et on lui fournit une politique d'arrondi (cf le PS3 en bas de mon post au sujet de l'encapsulation possible du type long_float)
    package my_general_money is -- on fournit au client un outil pour calculer des taux respectant scrupuleusement la politique d'arrondi fournie par my_round
     function "*" (rate : Long_Float; value : Decimal) return Decimal;
    end my_general_money;
    -- le fichier d'implantation de la fonction "*" 
    package body my_general_money is
     is 
     function "*" (rate : Long_Float; value : Decimal) return Decimal is
    ( my_round (rate * Long_Float (value)));
    end my_general_money;
    -- Voilà ! fini ! On dispose à ce stade d'une API générique complète pour gérer une monnaie quelconque liée à une politique d'arrondi donnée
    -- Un programme principal dans un troisième fichier pour tester ce module sur l'euro. Voilà ce qui se passe côté utilisateur de ce module:
    with ada.text_io; use ada.text_io; -- entrée sorties
    with my_general_money; -- notre API complète pour créer une monnaie
    procedure main is
    -- On instancie une monnaie à nous (virgule fixe)
     type Euro is delta 0.001 range -10.0**10 .. 10.0**10;
    -- On se donne une politique d'arrondi (ici l'arrondi par défaut fourni par Ada
     function round_euro (lf : Long_Float) return Euro is (Euro (lf)); 
    -- on instancie un package pour ajouter à notre Euro une politique d'application d'un taux
     package Euros is new my_general_money (Euro, round_euro); use Euros;
    -- Voilà ce que le programmeur a à faire: 3 lignes pour décrire ses contraintes et sa politique d'arrondi. Il a maintenant tout pour jouer avec sa monnaie
     e1 : Euro := 3.0; -- expression littérale très naturelle pour le programmeur
     e2 : Euro; 
     rate : Long_Float := 2.0/3.0; -- une baisse de 33.333.... pourcents
    -- rappelons qu'un taux n'est pas une valeur monétaire mais (idéalement) un réel.
    begin
     e2 := rate * e1;
     put_line (e2'img); -- affichera exactement 2.000 (c'est à dire que la sortie tient compte de la précision en vigueur sans rien avoir à faire
    end Main;

    Ce qu'on gagne à faire ça:

    • On dispose d'une API complète parfaitement encapsulée de haut niveau où le programmeur fournit simplement sa politique d'arrondi (on peut même en mettre une par défaut si on veut, ou même lui filer dans l'API une dizaine de modèles codées par celui écrit l'API une fois pour toutes).
    • Le code reflète exactement la nature du problème mathématique. Le code contient du sens et donc:
    • Le programmeur (client de l'API) est obligé de faire correctement les choses: il est obligé de faire la distinction entre les types des objets qu'il manipule. Il ne peut par exemple pas écrire un Euro * un Euro. Mais il peut bien évidemment écrire un entier * un Euro,ou un Euro +/- un Euro et il est certain que le calcul sera rigoureusement exact. Dans cette approche on peut écrire ce qui a du sens, on ne peut pas écrire ce qui n'en a aucun dans le cadre de l'API fournie.
    • Il est assuré qu'au moindre calcul hors plage de sa monnaie, une exception sera levée (contrairement au décimal flottant qui serait silencieux: danger !!!)
    • Il ne peut pas écrire à la main e := 2.0001 sans se prendre une erreur du compilateur qui lui signifie que la valeur entrée est stupide puisqu'elle exige une précision supérieure à celle qu'il a exigée dans son type
    • Il n'a rien eu à coder à part sa fonction d'arrondi si elle n'est pas standard (c'est bien le minimum légal, sans jeu de mot :) )
    • Chaque fois qu'un changement de politique d'arrondi se produit, il n'a qu'une seule fonction à coder, il n'a rien d'autre à toucher nulle part dans le code.
    • On n'a même pas besoin de coder les entrées/sorties: le formatage est géré tout seul parce que ada écrit correctement de la virgule fixe et ça rappelle, à l'affichage, le nombre de chiffres correspondant à la précision souhaitée.
    • Le client de l'API n'a même pas à faire appel lui même à sa fonction d'arrondi. C'est l'API qui le fait pour lui.
    • Pour le calcul de taux, que le long_float soit binaire ou décimal n'a aucune importance concernant l'exactitude des résultats : on obtiendra toujours la même chose à la fin avec l'un ou l'autre contrairement à ce qui a été insinué plusieurs fois dans ce fil. Le programmeur n'a même pas à savoir comment c'est géré par Ada: Ada lui garantit simplement que les specs sont respectées.

    Ceci répond à tous les souhaits exprimés par Dring (y compris les conversions entre devises); Une API de type Decimal (qui a été mentionnée comme solution pour les problèmes de Dring) ne pourra jamais bénéficier de tous ces avantages car trop silencieuse / permissive (même sémantiquement on peut y écrire Decimal("0.1") fois Decimal("0.000001") sans se prendre le moindre warning puisque Python n'a aucune indication dans le code du 'sens' qu'il faut donner à ces expressions ; Et puis, pour le programmeur, comme le disait Dring, c'est franchement pas agréable de se traîner à chaque calcul l'instanciation d'une classe à partir de chaînes de caractères. Côté client, difficile de faire plus fluide, plus naturel ou plus précis.

    PS1
    J'ai mis plus de temps à écrire le texte de ce commentaire qu'à coder l'API (qui prend 5mn).
    PS2
    J'espère que ce post aura clarifié les incompréhensions réciproques.
    PS3
    Si on veut laisser au client de l'API la possibilité de coder son arrondi on ne peut lui cacher le long_float. Mais si on lui fournit une centaine de politiques d'arrondi directement dans notre API, ou si on lui code selon ses souhaits, on pourra même lui cacher le long_float.