Bizarrerie sur des doubles

AlainAlain Membre
08:33 modifié dans API AppKit #1
Bonjour,

Tout nouvau en Objective-c, j'ai commencé par la lectures des doc Apple et surtout par leur tutoriel "Currency Converter"
(pour memoire : deux champs "taux de change" et "Montant à  changer", un champ "Resultat" et un bouton pour lancer la conversion
Tout a fonctionné jusqu'au moment où je l'ai utilisé: taux = 1,2; montant = 1,3; résultat = 1,560000061988831; Ce qui ne me semble pas satisfaisant.
En examinant les variable je vois 1,2 devient 1,20000005; 1,3 devient 1,29999995; le produit 1,56000006 devient ce qui est affiché.
Je constate donc une "erreur" lors du passage de l'affichage à  la valeur utilisée et vice-versa.

Pouvez-vous me dire comment cela se fait, et comment cela peut être corrigé
Merci

Alain

Réponses

  • psychoh13psychoh13 Mothership Developer Membre
    08:33 modifié #2
    Le standard IEEE 754 qui définit la façon de coder les nombres à  virgules flottantes, donne une bonne technique pour stocker des nombres, cependant cette technique n'est pas très précise...
    Pour que ça t'affiche un résultat plus propre il faut que tu utilises un NSNumberFormatter que tu configures pour arrondir les nombres et avoir quelque chose de plus satisfaisant.

    À part ça, bienvenu sur Objective-Cocoa.org. ;)
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #3
    Rien de bizarre dans ce résultat.

    Test: Quand on fait tous les tests (100.*a,bc == abc )? pour a, b, c des chiffres de 0 à  9, il y a 6,9% d'erreurs

    et c'est normal !
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #4
    dans 1229709306:

    En examinant les variable je vois 1,2 devient 1,20000005; 1,3 devient 1,29999995; le produit 1,56000006 devient ce qui est affiché.

    Là , tu dois déclarer ta variable en float et tenter de l'écrire avec une précision trop importante:
        float x=1.2;
        printf("%.8f",x);

    La précision d'un float n'est pas de 8 chiffres après la virgule.
    Tu n'as pas cette erreur en double, il faut utiliser plus de chiffres après la virgule pour provoquer une erreur semblable.
    Ceci vient du fait que l'écriture en binaire de 1/5  demande une infinité de chiffres : 5 n'est pas une puissance de 2.
  • ChachaChacha Membre
    08:33 modifié #5
    ça n'est pas adapté au sujet (trop complexe), mais ça vaut le coup d'être casé ici pour les futurs lecteurs de passage :
    what every computer scientist should know about floating-point arithmetic est une référence pour toutes les explications relatives à  l'encodage des nombres à  virgule, et des problèmes qui vont avec.

    +
    Chacha
  • AliGatorAliGator Membre, Modérateur
    décembre 2008 modifié #6
    Dans le lot y'a Cet article de RidiculousFish aussi ;)

    D'ailleurs sur ce blog y'a 2 ou 3 autres articles intéressants (même s'il est plus trop mis à  jour), genre sur comment Cocoa optimise les classes de types collection et array, ou les memory-barrier dans les applis multi-cores ou multi-threading, ...
  • fouffouf Membre
    08:33 modifié #7
    dans 1229982524:

    ça n'est pas adapté au sujet (trop complexe), mais ça vaut le coup d'être casé ici pour les futurs lecteurs de passage :
    what every computer scientist should know about floating-point arithmetic est une référence pour toutes les explications relatives à  l'encodage des nombres à  virgule, et des problèmes qui vont avec.

    +
    Chacha


    J'ai pas eu le temps de tout lire, mais l'article que tu proposes a l'air vraiment excellent. Merci beaucoup ;)
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #8
    Et c'est pas tout , en dehors de la raréfaction de la représentation des flottants , on a la pathologie  des calculs en flottants :

    #include <stdio.h>
    #include <math.h>
    #define MAX(a,b) ( (a)>(b) ? (a) : (b) )
    int main(void){
    double a=-0.999950034599999982 , b=1.01000000000000001;
    double c=-1.01005054,d=1.0;
    double m11=100,m12=101,m21=99,m22=100;

    printf( "a = c à  %.0f %% près\n",fabs((c-a)/a)*100.);
    printf( "b = d à  %.0f %% près\n",fabs((d-b)/b)*100.);

    printf("Calcul de [m11,m12;m21,m22]*[a;b] = [%.4f,%.4f]\n",m11*a+m12*b,m21*a+m22*b);
    printf("Calcul de [m11,m12;m21,m22]*[c;d] = [%.4f,%.4f]\n",m11*c+m12*d,m21*c+m22*d);

    printf("Soit un rapport abscisse/abscisse ou ordonnée/ordonnée = %.0f %%\n",
    100.*MAX((m11*a+m12*b)/(m11*c+m12*d) , (m21*a+m22*b)/(m21*c+m22*d)));
    return 0;
    }

    % pgm
    a = c à  1 % près
    b = d à  1 % près
    Calcul de [m11,m12;m21,m22]*[a;b] = [2.0150,2.0049]
    Calcul de [m11,m12;m21,m22]*[c;d] = [-0.0051,0.0050]
    Soit un rapport abscisse/abscisse ou ordonnée/ordonnée = 40127 %
    %


    Embêtant , non ?
    Des exemples comme celui-là , on peut en pondre autant qu'on en veut !
  • AliGatorAliGator Membre, Modérateur
    08:33 modifié #9
    Heu attention avec ton exemple Philippe, je ne suis pas sûr que ta matrice soit bien conditionnée justement. Une matrice M mal conditionnée peut aussi mener à  ce genre de résultat (le choix d'une matrice mal conditionnée pour ton exemple est peut-être voulu d'ailleurs ?), puisque par définition alors M*x et M*(x+dx) auront des valeurs très différentes l'une de l'autre... Alors qu'avec une matrice N mieux conditionnée, une erreur de dx sur le vecteur sur lequel tu appliques ta matrice N aura beaucoup moins d'impact.

    Donc après j'imagine que tu as aussi choisi un exemple exprès de matrice mal conditionnée (||M|| * ||M^-1|| très grand --> résultat de l'application de M sur un vecteur quelconque très sensible aux perturbations/erreur sur ce vecteur) pour mettre en avant et grossir le problème. Mais ici ça n'est pas alors forcément dû aux floats (ou du moins pas que) mais aussi et surtout au choix de ta matrice.

    En général si on est amené à  faire des calculs matriciels par exemple justement on prend soin de prendre des matrices correctement conditionnées... C'est pareil pour les rotation, c'est de là  que sont aussi nés les quaternions pour exprimer les rotations plutôt que de passer par des matrices de transformations lassiques d'ailleurs : d'une part pour rajouter la notion de sens de la rotation (+90 et -270° n'a pas le même sens) mais aussi pour éviter les problèmes de précision et d'inversion de polarité lorsqu'on tourne trop près de l'axe, etc...
    Conclusion : dans ce genre de domaine faut toujours avoir à  l'esprit la prise en compte de l'erreur dans les calculs... et calculer cette erreur aussi ou s'assurer de son impact.
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #10
    dans 1229990054:

    Une matrice M mal conditionnée
    ....
    Conclusion : dans ce genre de domaine faut toujours avoir à  l'esprit la prise en compte de l'erreur dans les calculs... et calculer cette erreur aussi ou s'assurer de son impact.


    Je vois que tu te souviens de tes cours, cela fait plaisir au vieux prof de math que je suis.
    Il s'agit effectivement d'une matrice mal conditionnée, et les résultats obtenus ne sont pas les résultats d'erreurs de calculs, mais la description de la réalité.
    Mais au vu de la matrice
        [ 100   101 ]
        [  99    100]
    qui se doute qu'il y a un piège dans cette matrice ?
    Qui se doute que faire une approximation à  deux chiffres après la virgule peut nuire grave ?


    La présentation du conditionnement dans la littérature par les normes   ||M|| * ||M^-1|| très grand est une complication intellectuelle qui rend obscur un phénomène tout simple de valeurs propres :
    On a ici une matrice M admettant deux vecteurs u et v réalisant Mu~200u et Mv~0.005v. En combinant u et v on provoque une distorsion dans les calculs, qui n'est pas une erreur.

    En informatique graphique, les matrices utilisées sont des matrices de rotation , de symétrie, et d'échelle (scaleXBy: yBy;), et elles ne présentent pas ce genre de pathologie (sauf à  le faire exprès pour le matrices d'échelle).   

  • AliGatorAliGator Membre, Modérateur
    08:33 modifié #11
    Oui donc si je me souviens bien du coup l'ellipse résultat de l'application de M sur le vecteur unitaire du coup devrait être très longue (200) et très étroite (0.005) au vu des valeurs propres de M... En effet donc trè écrasé, ce qui explique que la moindre variation du vecteur d'entrée a de grosses conséquences sur le résultat en sortie ;)

    Donc confirme-moi, c'est bien le fait que tes VP de ta matrice sont assez éloignées l'une de l'autre (l'une très grande par rapport à  l'autre) que ta matrice est mal conditionnée ? Car justement les composantes principales de ta matrice sont dans un rapport élevé l'une par rapport à  l'autre ?
    (Et donc que du coup la moindre petite erreur d'approximation peut avoir en effet de l'influence...)

    Ah ça fait drôle de ressortir tout ça, ça rappelle des souvenirs :)
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #12
    Les outils de math.h sur les flottants

    Voici un petit programme pour écrire les deux plus petits double positifs
    % pgm
    Le plus petit double positif est 0.500000 2^-1073
    Le suivant est 0.500000 2^-1072
    %


    #include <stdio.h>
    #include <math.h>
    /* Quelques fonctions d'exploration de math.h
    Comparer
    fmin, fmax, fdim:  fdim(x,y) = max{x-y,0}.
    fabs, copysign: copysign(x,y) attribue à  x le signe de y.
    nextafter, nexttoward: nextafter(x,y) est le réel suivant x dans la direction de y.

    Les fonctions liées à  l'écriture exposant-mantisse
    modf, logb, ilogb, frexp: Décompose x en x' * 2^n.
    ldexp, scalbn, scalbln:  calcul de x'*2^n.
    Comme de coutume, on peut ajouter les mêmes fonctions avec le suffixe l pour les long double, et f pour les float
    */

    int main(void){
    double x=nextafter(0.0,1.0);
    int exponent;
    double y=frexp(x,&exponent);
    printf( "Le plus petit double positif est %f 2^%d\n",y,exponent);

    x=nextafter(x,1.0);
    y=frexp(x,&exponent);
    printf( "Le suivant est %f 2^%d\n",y,exponent);

    return 0;
    }
  • Philippe49Philippe49 Membre
    08:33 modifié #13
    dans 1230021519:

    Donc confirme-moi, c'est bien le fait que tes VP de ta matrice sont assez éloignées l'une de l'autre (l'une très grande par rapport à  l'autre) que ta matrice est mal conditionnée ? Car justement les composantes principales de ta matrice sont dans un rapport élevé l'une par rapport à  l'autre ?
    (Et donc que du coup la moindre petite erreur d'approximation peut avoir en effet de l'influence...)

    Ah ça fait drôle de ressortir tout ça, ça rappelle des souvenirs :D


    C'est exactement cela, le quotient des valeurs propres est très grand par rapport à  1, et cela rend la matrice mal conditionnée.
    Pour une rotation ou une symétrie ce quotient est de 1, c'est-à -dire l'idéal et ce genre de situations ne se produira pas.
    Pour une matrice d'échelle, cela ne se produira que si on fait exprès de prendre le quotient (rapport en x/rapport en y) très grand (ou très petit). 
  • tabliertablier Membre
    08:33 modifié #14
    Bienvenu sur Objective-cocoa.
    Et malgré ces problèmes de math, restons philosophe mon cher Alain.  :P
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #15
    Raréfaction de la représentation des flottants en machine

    Sur la base du programme quelques posts plus haut, voici la situation sur les premiers flottants positifs :


    +++++++++++++++++ Pour l'exposant -1073 , on trouve 1 flottants
    +++++++++++++++++ Pour l'exposant -1072 , on trouve 1 flottants
    +++++++++++++++++ Pour l'exposant -1071 , on trouve 2 flottants
    +++++++++++++++++ Pour l'exposant -1070 , on trouve 4 flottants
    +++++++++++++++++ Pour l'exposant -1069 , on trouve 8 flottants
    +++++++++++++++++ Pour l'exposant -1068 , on trouve 16 flottants
    +++++++++++++++++ Pour l'exposant -1067 , on trouve 32 flottants
    +++++++++++++++++ Pour l'exposant -1066 , on trouve 64 flottants
    +++++++++++++++++ Pour l'exposant -1065 , on trouve 128 flottants
    +++++++++++++++++ Pour l'exposant -1064 , on trouve 256 flottants
    +++++++++++++++++ Pour l'exposant -1063 , on trouve 512 flottants


    sans surprise ...
  • Philippe49Philippe49 Membre
    08:33 modifié #16
    dans 1230022199:

    Et malgré ces problèmes de math, restons philosophe mon cher Alain.  :P


    Et malgré grâce à  ces problèmes de math, restons philosophe mon cher Alain.  :P
  • AlainAlain Membre
    08:33 modifié #17
    Bonjour,

    Merci de toutes vos réponses. Je dois avouer que certaines m'ont rappelé quelques vieux cours de math, qui ont malgré tout eu quel mal à  remonter à  la surface.. et je reste philosophe.

    Je me doutais bien qu'il y a là  deux effets de bord : l'un lié à  la conversion de texte en réel (et vice-versa), l'autre aux erreurs de multiplication. Et je concevais qu'un simple arrondi à  deux ou trois décimales (voire quatre ou cinq) résolvait le pb...
    Et j'ai eu la réponse : les "formateurs".

    Tout est donc bien.
    Merci

Connectez-vous ou Inscrivez-vous pour répondre.