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écificationsgenerictypeDecimalisdelta<>;-- on fournira, à l'instantiation du package, la précision et la plagewithfunctionmy_round(float_value:Long_Float)returnDecimal;-- 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)packagemy_general_moneyis-- on fournit au client un outil pour calculer des taux respectant scrupuleusement la politique d'arrondi fournie par my_roundfunction"*"(rate: Long_Float;value: Decimal)returnDecimal;endmy_general_money;-- le fichier d'implantation de la fonction "*" packagebodymy_general_moneyisisfunction"*"(rate: Long_Float;value: Decimal)returnDecimalis(my_round(rate*Long_Float(value)));endmy_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:withada.text_io;useada.text_io;-- entrée sortieswithmy_general_money;-- notre API complète pour créer une monnaieproceduremainis-- On instancie une monnaie à nous (virgule fixe)typeEuroisdelta0.001range-10.0**10..10.0**10;-- On se donne une politique d'arrondi (ici l'arrondi par défaut fourni par Adafunctionround_euro(lf: Long_Float)returnEurois(Euro(lf));-- on instancie un package pour ajouter à notre Euro une politique d'application d'un tauxpackageEurosis newmy_general_money(Euro, round_euro);useEuros;-- 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 monnaiee1:Euro:=3.0;-- expression littérale très naturelle pour le programmeure2: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.begine2:=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 à faireendMain;
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.
[^] # Re: Il faut bien lire ce qu'on lit!
Posté par snowball (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 :
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:
Ce qu'on gagne à faire ça:
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.