IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Précision des calculs


Sujet :

C++

  1. #1
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2014
    Messages
    29
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2014
    Messages : 29
    Par défaut Précision des calculs
    Bonjour,

    J'essai de calculer la valeur de sin(x) (et de cos(x)) avec une série de Taylor et plus la valeur de x augmente plus l'erreur de calcul est élevée ce qui est normal je sais... Ma question est, comment du code C++ sur un ordinateur n'est pas plus précis que ma calculatrice??? Y a-t-il une façon de contourner cela? J'aimerais au moins avoir la même quantité de chiffre significatif que ma calculatrice..

    Mes variables sont en double..

    Merci infiniment!

    PS. mon programme contient d'autres partie que j'ai effacé pour simplifier alors ne vous attardez pas sur les "switch/case" vides...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
     
    #include <iostream>
    #include <iomanip>
     
     
    using namespace std;
     
    void main (void)
    {
        double CalculFactoriel( int Exposant);
        double CalculPuissance( double X, int Exposant);
        int Resultat, Exposant, Signe;
        double ResultatSin, ResultatCos, X;
        double Factoriel, Puissance;
     
     
            cout << "Votre choix : ";
        cin >> Choix;
        cin.sync();
     
     
     
     
        switch(Choix)
        {
        case 1:        break;
     
        case 2:         break;
     
        case 3:  system("cls");
                cout << "Calculons la valeur de sin(x)"<< endl << "Entrer la valeur de x = " << endl;
                cin.sync();
                cin >> X;
                cout<<endl<<endl;
     
                ResultatSin=0;
                            for(int n=0; n<=15; n++)
                             {
                                Exposant = (2*n)+1;
                                Factoriel = CalculFactoriel( Exposant );
                                Puissance = CalculPuissance( X, Exposant);
                                if( n%2 == 0)
                                {
                                    Signe = 1;
                                }
                                if( n%2 != 0)
                                {
                                    Signe = -1;
                                }
                                ResultatSin += (Signe*Puissance)/Factoriel;
     
     
                             }
                cout << setprecision (9) << ResultatSin <<endl;
                break;
     
        case 4: system("cls");
                cout << "Calculons la valeur de cos(x)"<< endl << "Entrer la valeur de x = " << endl;
                cin.sync();
                cin >> X;
                cout<<endl<<endl;
     
                ResultatCos=0;
                            for(int n=0; n<=15; n++)
                             {
                                Exposant = (2*n);
                                Factoriel = CalculFactoriel( Exposant );
                                Puissance = CalculPuissance( X, Exposant);
                                if( n%2 == 0)
                                {
                                    Signe = 1;
                                }
                                if( n%2 != 0)
                                {
                                    Signe = -1;
                                }
                                ResultatCos += (Signe*Puissance)/Factoriel;
     
     
                             }
                cout<< setprecision (9)<< ResultatCos <<endl;
                break;
     
        case 5: break;
     
        case 6: break;
        }
     
        system("pause");
      }
     
    double CalculFactoriel( int Exposant)
    {
        float ResulatFactoriel=1;
        while (Exposant > 0 )
        {
            ResulatFactoriel *= Exposant;
            Exposant=Exposant-1;
     
        }
        return ResulatFactoriel;
     
    }
    double CalculPuissance( double X, int Exposant)
    {
        int compteur=1;
        double total=1;
        while (compteur<=Exposant)
        {
            total = total*X;    
     
            compteur=compteur++;
     
        }
        return total;
    }

  2. #2
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    La réponse tient en deux "détails": le codage des nombres flottants, et la taille du mot des processeurs.

    Un nombre flottant est imprécis, parce qu'on ne garde que deux informations: une puissance de deux, et une fraction entière en base 2, qu'on appelle mantisse (cf le standard IEEE 754 sur wikipedia)

    Cela posé, la précision est déterminé par la taille du type manipulé.
    Si ta précision est la même, c'est parce que le nombre dans ta calculatrice doit être de la même taille que le double de ton PC.

    Avec windows, essaie le long double, qui aura une meilleur précision théorique.

    Cela dit, pour du calcul numérique précis, il vaut mieux utiliser une bibliothèque de mathématiques, utilisant des nombre réels exacts.
    Sujet que je ne connais que par la théorie, et pour lequel je n'ai pas de référence.

  3. #3
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Citation Envoyé par leternel Voir le message
    Avec windows, essaie le long double, qui aura une meilleur précision théorique.
    Attention, ce n'est le cas que sous GCC. Et j'espère que son implémentation des flux standards C++ n'utilise pas celle de Visual comme l'implémentation des flux standards du C le fait, autrement le long double ne sera pas affichable correctement sans le re-caster en double d'abord.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  4. #4
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Citation Envoyé par leternel Voir le message
    Cela dit, pour du calcul numérique précis, il vaut mieux utiliser une bibliothèque de mathématiques, utilisant des nombre réels exacts.
    Sujet que je ne connais que par la théorie, et pour lequel je n'ai pas de référence.
    GMP - The GNU Multiple Precision Arithmetic Library (qui a un wrapper Boost : Boost.multiprecision).

  5. #5
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2014
    Messages
    29
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2014
    Messages : 29
    Par défaut
    Merci beaucoup pour les réponses!

    Je savais que la bibliothèque <cmath> me donnerait la réponse exacte mais le défi était là, ne pas utiliser cette bibliothèque.

    J'ai changé mes variables pour des long double ce qui a effectivement amélioré la précision mais uniquement "pour 1 valeur". Je veux dire, avec double et setprecision (9), sin(7) avait les 3 derniers chiffres différents alors que maintenant ils sont pareils mais le problème recommence avec sin(8). Évidemment, l'erreur est maintenant plus petite et "repoussée" mais toujours présente.

    Rendu là, je suis satisfait car je suis "anyway" limité par la précision machine alors c'est excellent!

    Bref, MERCI! :-)

  6. #6
    Membre éprouvé

    Profil pro
    Inscrit en
    Juin 2013
    Messages
    114
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 114
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par guitalex Voir le message
    J'ai changé mes variables pour des long double ce qui a effectivement amélioré la précision mais uniquement "pour 1 valeur". Je veux dire, avec double et setprecision (9), sin(7) avait les 3 derniers chiffres différents alors que maintenant ils sont pareils mais le problème recommence avec sin(8). Évidemment, l'erreur est maintenant plus petite et "repoussée" mais toujours présente.
    La précision des double, c'est 16 chiffres significatifs, si tu as des erreurs à partir du neuvième, c'est que ton problème ne vient pas de la représentation des nombres mais de ton algorithme, de la façon dont tu calcules la série de Taylor, en fait.

    Pour des valeurs de X un peu élevées, les valeurs absolues des premiers termes des séries trigonométriques X^n/n! croissent avant de décroitre (jusqu'à n=(int) X). Du coup, avec la méthode actuelle, où tu calcules une somme alternée de termes qui peuvent être importants, tu perds de la précision par troncature dès que X dépasse quelques unités (parce que tu perd des décimales à chaque soustraction de deux grands nombres voisins), et tu auras des dépassements de capacité dès qu'il devient grand (parce que le plus grand terme de ta série est d'ordre X^X, donc exponentiel).

    Tu peux très facilement arranger les choses en réduisant X à un intervalle plus petit, idéalement [0,PI/4] en exploitant les symétries des fonctions sinus et cosinus, et le fait que cos(PI/2-x)= sin(x) (et inversement). La transformation est très facile à faire (un fmod() et un test). Ta série de Taylor se calculant sur des valeurs de X inférieures à 1, elle va converger beaucoup plus vite, et avec beaucoup moins d'erreurs d'arrondi. Rien qu'avec cela, tu devrais gagner plusieurs décimales, et surtout n'avoir pas des résultats absurdes (voire des dépassements de capacité) quand tu essaies de calculer, par exemple, sin (2000 PI). Cela devrait te donner une précision voisine de la précision de la représentation virgule flottante, soit 5 ou 6 chiffres en float, et 14 ou 15 en double (plus que ta calculette).

    Tu peux encore améliorer cela en modifiant l'ordre des calculs. Par exemple, si tu sommes ta série à l'envers (des petites valeurs vers les grandes) et que tu travailles par paires de chiffres, tu vas sans doute gagner une décimale ou deux.

    Egalement, si tu tiens à utiliser des polynômes, tu peux faire mieux que la série de Taylor. Elle converge à l'infini, mais pour un degré donné, ce n'est pas la meilleure approximation polynomiale de ces fonctions. Tu trouveras de meilleures formules dans des livres (par exemple Handbook of Mathematical Functions, d'Abramowitz et Stegun), ou tu peux regarder les polynômes de Tchebitcheff. Là encore, le gain est plus faible que la réduction du domaine, mais si tu veux bien faire les choses, c'est là qu'il faut aller.

    Sinon, il y a des méthodes très efficaces pour calculer les sinus et les cosinus, qui ne passent pas par les polynômes. Tu peux encore regarder chez Abramowitz et Stegun, ou lire sur la méthode CORDIC.


    Mais de toutes façons, ton problème n'est pas la précision de la représentation flottante, et passer à une représentation en précision infinie pour compenser un mauvais algorithme, ce n'est pas très différent de remplacer ton calcul par une fonction de bibliothèque.

    En pratique, il y a très peu de cas (même en calcul numérique) où l'on a besoin d'une précision supérieure à double, si on fait attention aux algorithmes qu'on utilise.

    Francois

  7. #7
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Pour relativiser un peu, les nombres flottants en informatique c'est plein de pièges, ça fait rarement ce que l'on pense. Voici un programme pour illustrer ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // g++ -Wall -Wextra -Wconversion -Wsign-conversion -std=c++11 -pedantic -fopenmp main.cpp -o main && ./main
    // g++ -Wall -Wextra -Wconversion -Wsign-conversion -std=c++98 -pedantic -fopenmp main.cpp -o main && ./main
     
    #include <iostream>
    #include <limits>
     
     
    int main()
    {
    	std::cout.precision(std::numeric_limits<double>::max_digits10);
     
    	double const d = 0.2;
    	std::cout << d << std::endl;
     
    	double sum = 0.0;
    	for (std::size_t i = 0; i < 10; ++i)
    	{
    		sum += d;
    		std::cout << sum << std::endl;
    	}
     
    	return 0;
    }
    Et le résultat qu'aucun "mathématicien" aurait pu trouver :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0.20000000000000001
    0.20000000000000001
    0.40000000000000002
    0.60000000000000009
    0.80000000000000004
    1
    1.2
    1.3999999999999999
    1.5999999999999999
    1.7999999999999998
    1.9999999999999998

  8. #8
    Membre éprouvé

    Profil pro
    Inscrit en
    Juin 2013
    Messages
    114
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 114
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par Ehonn Voir le message
    Pour relativiser un peu, les nombres flottants en informatique c'est plein de pièges, ça fait rarement ce que l'on pense. Voici un programme pour illustrer ceci :

    (...)

    Et le résultat qu'aucun "mathématicien" aurait pu trouver :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0.20000000000000001
    0.20000000000000001
    0.40000000000000002
    0.60000000000000009
    0.80000000000000004
    1
    1.2
    1.3999999999999999
    1.5999999999999999
    1.7999999999999998
    1.9999999999999998
    Je ne suis pas certain de voir le piège, ici: tu as une erreur qui porte sur la 17eme décimale, donc au delà de la précision nominale des double, cette erreur ne s'accroit pas avec les itérations, et les fonctions usuelles arrondiront correctement le résultat. Ca me parait au contraire un assez bon exemple du fait que l'arithmétique en virgule flottante marche (dans le sens où elle permet des calculs corrects, à la précision de la représentation près), non?

    Ensuite, il y a des pièges, mais une fois qu'on a compris que

    - la représentation est approchée, donc les tests d'égalité se font toujours à une "tolérance" près
    - quand on fait des calculs "classiques", on perd de la précision quand on soustrait deux termes voisins, il faut éviter ce genre de situation
    - quand on fait des maths un peu compliquées, cela peut se corser, et il est prudent de s'appuyer sur des algorithmes connus

    on ne craint pas grand chose.

    Francois

  9. #9
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Citation Envoyé par fcharton2 Voir le message
    Ca me parait au contraire un assez bon exemple du fait que l'arithmétique en virgule flottante marche (dans le sens où elle permet des calculs corrects, à la précision de la représentation près), non?
    Ben justement non. L'année dernière un débutant avait un souci avec une boucle for qui utilisait un float, il avait une itération de plus que prévu car 1.99 est < à 2.0.
    On peut toujours rentrer dans un débat du genre : « Faut-il utiliser les double uniquement pour du calcul numérique ? »

    Au passage, si l'arrondi des nombre à virgule était si simple, il n'y aurait ni autant de recherche autour, ni des bibliothèques comme cadna.

  10. #10
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Tu peux encore améliorer cela en modifiant l'ordre des calculs. Par exemple, si tu sommes ta série à l'envers (des petites valeurs vers les grandes) et que tu travailles par paires de chiffres, tu vas sans doute gagner une décimale ou deux.
    Interlude de maths: Il faut faire attention si on veut ré-ordonner les calculs. En effet, si la série est semi convergente, le théorème de réarrangement de Riemann nous assure qu'on va PAS avoir le résultat voulu, indépendant des histoires de flottants.Ici, la série est absolument convergente donc on s'en fout, mais faut garder ca en tête.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  11. #11
    Membre éprouvé

    Profil pro
    Inscrit en
    Juin 2013
    Messages
    114
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 114
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par Davidbrcz Voir le message
    Interlude de maths: Il faut faire attention si on veut ré-ordonner les calculs. En effet, si la série est semi convergente, le théorème de réarrangement de Riemann nous assure qu'on va PAS avoir le résultat voulu, indépendant des histoires de flottants.Ici, la série est absolument convergente donc on s'en fout, mais faut garder ca en tête.
    Tu es certain que ce théorème s'applique à une somme finie?

    Parce que si la série de Taylor est infinie, la formule d'approximation qu'on calcule ici ne fait intervenir qu'un nombre fini de termes. Et bien que je n'ai pas d'article Wikipédia à citer à l'appui de cette affirmation, il me semble qu'aux problèmes de représentation flottante près, sa somme sera la même quel que soit l'ordre dans lequel on effectue le calcul, et ce quelle que soit la nature de la série.

    Francois

Discussions similaires

  1. Calculer la précision des coordonnées
    Par saxrub dans le forum IGN API Géoportail
    Réponses: 3
    Dernier message: 28/11/2013, 08h25
  2. Réponses: 12
    Dernier message: 03/01/2012, 13h49
  3. scipy.poly1d : précision du calcul des racines
    Par ryced dans le forum Calcul scientifique
    Réponses: 5
    Dernier message: 25/01/2010, 10h04
  4. Faire des calculs automatiques en JavaScript
    Par yoyot dans le forum Général JavaScript
    Réponses: 7
    Dernier message: 28/02/2005, 10h31
  5. Réponses: 8
    Dernier message: 18/09/2002, 03h20

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo