• [^] # 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é à 2.

    Sommaire

    Sur le fond

    Virgule fixe / flottant binaire / flottant décimal ? Quel choix quand on veut représenter des valeurs monétaires en machine ?

    Appelons D le type censé représenter notre monnaie. Qu'attendons nous de lui ?

    1. D doit pouvoir représenter de façon exacte les nombres de la forme k/100 où k est un entier compris dans un intervalle fixé borné (ici on pourrait se contenter de k inférieur à 1 million de milliards soit 10 puissance 15).

    2. On doit pouvoir, de façon exacte, additionner / soustraire dans D.

    3. Comme on ne multiplie pas des valeurs de D entre elles mais qu'on veut pouvoir prendre des fractions de D il nous faut pouvoir multiplier un float par un élément de D et obtenir un élément de D. On doit donc disposer en particulier d'une fonction d'arrondi de float vers D qui respecte scrupuleusement des règles fixées (éventuellement plus complexes que les 3 modes que propose les différentes normes IEEE754 décimal ou binaire). Celle-ci devra donc être codée par le programmeur en fonction des spécifications.

    4. Il serait bon, bien que non indispensable, que le programmeur puisse entrer des expressions littérales usuelles directement dans D et surtout, que le compilateur empêche des écritures qui sortent des attendus ci-dessus, et oblige le cas échéant au programmeur de forcer les choses par du casting explicite.

    Pour faire matheux, D doit donc posséder une structure (exacte) de Z-module et représenter des valeurs uniformément réparties d'un intervalle fixé (dont le diamètre ne dépasse pas 10 puissance 15). Cette description n'est pas celle des flottants (ulp variable, dynamique inutile pour représenter de la monnaie), par contre c'est exactement la description de la virgule fixe qui a été conçue spécialement pour ça.

    Une implantation rapide en Ada pour éclairer davantage

    with ada.text_io; use ada.text_io; 
    procedure my_dec is
     -- Déclaration de notre type stockant la monnaie
     type dec is delta 0.01 range -10.00**16 .. 10.00**16;
     for dec'small use 0.01;
     -- Description des règles d'arrondi, à modifier en fonction des spécifications 
     -- Ici on choisit l'arrondi au centime couramment utilisé évoqué dans le lien que tu as donné
     function round (u : long_float) return dec is
     function rounding_positive (u : long_float) return dec is
     d2 : dec := dec (100.0 * u); 
     d1 : dec := 100 * dec (u);
     begin
     return (if d2 -d1 >= 0.5 then 0.01 * d1 + dec'small else 0.01 * d1);
     end rounding_positive;
     begin
     return (if u>=0.0 then rounding_positive (u) else - rounding_positive (-u));
     end round; 
     -- On ajoute une opération permettant d'appliquer des taux (flottants) à notre monnaie
     function "*" ( rate : long_float; d : dec) return dec is (round (rate * long_float (d)));
     value : dec := 100.0;
     d : dec := (1.0 / 3.0) * value;
    begin
     put_line (d'img); -- affiche 33.33 comme attendu
     d := (2.0/3.0) * value;
     put_line (d'img); -- affiche 66.67 comme attendu
     d := 1.05* dec (0.7);
     put_line (d'img); -- affiiche 0.74 comme attendu
     d := 0.01;
     while d <= 0.05 loop -- fait exactement 6 tours comme attendu 
     put_line (d'img);
     d := d + 0.01;
     end loop;
     d := round (0.004); 
     put_line (d'img); -- affiche 0.00 comme attendu
     d := round (0.005); -- affiche 0.01 comme attendu
     put_line (d'img);
     d := 10_000_000_000_000_000.0 + 0.01;
     put_line (d'img); -- affiche 10_000_000_000_000_000.01 comme attendu
     d := 0.001; -- provoque une erreur de compilation explicite
     d := 
    end my_dec;

    Trois commentaires

    1. ma fonction d'arrondi correspond à une spécification particulière, mais le programmeur ne pourra faire l'économie d'écrire la fonction correspondant aux politiques d'arrondi qu'il a à respecter. C'est le seul moment où j'ai écrit du code "compliqué". Tout le reste est out-of-the-box et en terme d'API, c'est plus efficace (car plus safe au niveau code) que d'utiliser du flottant qui sera, par nature silencieux sur des écritures absurdes et qui sera également silencieux quand on dépasse les bornes de l'intervalle (alors qu'il faudrait soit empêcher la compilation le cas échéant, soit lancer des exceptions à l'exécution). Ce sont des exigences qu'un programmeur est en droit d'attendre dans l'économat et que ne lui fournit pas le flottant (binaire ou décimale).
    2. dans la fonction d'arrondi on peut ajouter des post et pré conditions si on veut rajouter la vérification que la fonction fait bien qu'elle doit faire et que ses arguments soient dans des bornes acceptables (cf https://en.wikibooks.org/wiki/Ada_Programming/Contract_Based_Programming )
    3. Les exemples que j'ai donnés montrent, que sans le moindre effort, on peut répondre aux problèmes auxquels n'est censé répondre que le flottant décimal (en tout cas c'est ce qu'on pourrait croire en lisant le lien (IBM) que tu as donné.

    Sur la forme

    Tu me cites:

    Quand on a décidé de mal arrondir, c'est sûr, on arrondit mal, ça on est d'accord.

    ce qui me semble être une évidence et refléter ce que tu as fait en python. Puis tu me réponds:

    Non ce qui est reproché aux flottants binaires sont leur API si on veut travailler avec du décimal.

    Mauvaise foi ? On était parti d'erreurs factuelles sur le site d'IBM que tu as mentionné concernant le calcul de 0.70 x 1.05. Ils font deux erreurs: la première sur le résultat du calcul en utilisant des "double". L'autre en arrondissant au centième sans tenir compte du chiffre des millièmes en sous-entendant que ça serait la faute du binaire flottant (!!!!) (La preuve que c'est du grand n'importe quoi est dans mon petit programme ci-dessus: aucun problème à arrondir correctement) et là tu me redis un "Non" (un non à quoi au juste ?) et tu embrayes en disant que finalement c'est un problème d'API !

    Que l'on puisse présenter un type abstrait, représenté de manière sous-jacente via le type float primitf (en binaire) d'un langage donné, qui se comporte comme des décimaux : personne ne le nie. Mais à ce compte là, il te suffit de [tout reconstruire à la main]

    Le programme ci-dessus te montre qu'on est très loin de cette caricature tu ne crois pas ?
    J'écris ensuite qu'il faut spécifier pleinement et qu'alors on fait facilement ce qu'on a à faire et tu réponds:

    Oui et c'est ce qu'a fait l'auteur du site en question, à savoir Mike Cowlishaw, en rédigeant la norme IEEE-754 pour les décimaux flottants.

    Je parlais de ce que veut l'utilisateur (en économat en l'occurrence). Je ne parle pas de celui qui pond une norme générale (qui d'ailleurs ne fournira pas forcément la bonne fonction d'arrondi out of the box au banquier). J'imagine bien que Mike Cowlishaw a fait ce qu'il avait à faire quand il a rédigé sa norme. D'autre part, les arrondis gérés par sa norme ne portent pas sur la deuxième décimale qui intéresse le banquier mais sur le dernier ulp (donc il faudra coder la fonction d'arrondi un peu comme je l'ai fait probablement)

    Effectivement, c'est bien ce qu'il me semblait : tu n'as aucune idée des besoins et des attentes de ce domaine métier. ;-)

    Peut-être pourrais-tu nous rappeler précisément ces besoins. J'ai l'impression que ni toi ni moi ne les connaissons précisément. Je fais ce que je peux en raisonnant mais si tu as des choses plus précises n'hésite pas à en faire part, c'est toujours intéressant.

    Je ne dis pas du tout que le flottant décimal est inutile (j'imagine bien qu'il y a des cas d'utilisation où c'est pratique), je dis qu'il ne répond pas aux besoins de Dring ce qui est très différent.