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 :

Bug surcharge d'opérateur


Sujet :

C++

  1. #1
    Membre émérite

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Points : 2 328
    Points
    2 328
    Par défaut Bug surcharge d'opérateur
    Bonjour à tous

    Pour me faire la main sur le C++, j'ai décidé de faire une classe Tableau2D (bien que je sache qu'il existe des bibliothèques déjà faites pour ca).

    Je vous met le code de la classe (tableau.h) :
    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
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    #ifndef TABLEAU_H_INCLUDED
    #define TABLEAU_H_INCLUDED
     
    #include <assert.h>
    #include <vector>
     
    template <typename T>
    class Tab2D{
    	public:
    		Tab2D(int sizx, int sizy=1): _size_x(sizx), _size_y(sizy){
    			_tab = std::vector< std::vector< T > > (_size_x);
    			for (int i=0; i<_size_x; i++)
    				(_tab[i]).resize(_size_y);
    		};
     
    		// Constructeur de copie
    		Tab2D(const Tab2D& tab2){
    			_size_x=tab2.size(1); _size_y=tab2.size(2);
    			_tab = std::vector< std::vector <T> > (_size_x);
    			for (int i=0; i<_size_x; i++){
    				(_tab[i]).resize(_size_y);
        			for(int j=0; j<_size_y; j++)
        				_tab[i][j]=tab2(i,j);
        		}
    		};
     
    		~Tab2D(){		};
     
    	/// Surcharge de la fonction size de std::vector        
    	int size(int dir) const {
    		int res=0;
    		assert(dir==1 || dir==2);
    		res=_tab.size(); //size_x
    		if ( dir==2 && res>0)
    			res=_tab[0].size(); //size_y
    		return res;
    	}
     
    	void print(std::ostream &flux) const{
    		for (int j=0; j<_size_y; j++){
    			for (int i=0; i<_size_x; i++){
    				std::cout << _tab[i][j] << "  ";
    			}
    			std::cout << std::endl;
    		}
    	};
     
    	bool SameSize(Tab2D const& new_tab) const{
    		return (_size_x==new_tab._size_x && _size_y==new_tab._size_y);	
    	}
     
    	bool estEgal(Tab2D const& b, int debug=0) const{
    		bool res=false;
    		if (SameSize(b)){
    			res=true;
    			for (int i=0; i<_size_x && res; i++){ // && res, car le break ne sort que de la premiere boucle !
    				for (int j=0; j<_size_y; j++){
    					if ( b(i,j)!= _tab[i][j]) {
    						res=false;
    						if (debug==1){
    							std::cout << "Première difference identifiée :" << std::endl;
    							std::cout <<  "(" <<i << "," << j << ") --> " << _tab[i][j] << " / " << b(i,j) << std::endl;
    							}
    						break;
    					}
    				}
    			}
    		} else if (debug==1){
    			std::cout << "Tableau qui ne sont pas de même taille :" << std::endl;
    			std::cout <<  _size_x << " "<< _size_y << " / " << b.size(1) << " " << b.size(2) << std::endl;
    		}
    		return res;
    	};
     
    	// ------- Opérateur d'indexation -----
    	T & operator()(int index, int index2){return _tab[index][index2];}
    	const T & operator()(int index, int index2) const{return _tab[index][index2];}
     
    	/// Permet de pouvoir ecrire : tab = 3
    	void operator=( T nombre ){
    		for(int i=0 ; i<_size_x; i++){
    		 	for(int j=0 ; j<_size_y; j++)
    				_tab[i][j]=nombre;	
    			}
    	};
     
    	/// Permet de pouvoir ecrire : tab1 += tab2; /// A declarer dans la classe
        Tab2D& operator+=( Tab2D const& tab2){
        	assert(SameSize(tab2));
        	for(int i=0 ; i<_size_x; i++){
        		for(int j=0 ; j<_size_y; j++)
        			_tab[i][j]+=tab2(i,j);
        	}
        	return *this;    
        };
     
        /// Permet de pouvoir ecrire : tab1 -= tab2; /// A declarer dans la classe
        Tab2D& operator-=( Tab2D const& tab2){
        	assert(SameSize(tab2));
        	for(int i=0 ; i<_size_x; i++){
        		for(int j=0 ; j<_size_y; j++)
        			_tab[i][j]-=tab2(i,j);
        	}
        	return *this;    
        };
     
     
        //// Permet de pouvoir ecrire : tab1 += 30 ;
        Tab2D& operator+=(T nb){
        	for(int i=0 ; i<_size_x; i++){
        		for(int j=0 ; j<_size_y; j++)
        			_tab[i][j]+=nb;
        	}
        	return *this;
        };
     
        //// Permet de pouvoir ecrire : tab1 -= 30 ;
        Tab2D& operator-=(T nb){
        	for(int i=0 ; i<_size_x; i++){
        		for(int j=0 ; j<_size_y; j++)
        			_tab[i][j]-=nb;
        	}
        	return *this;
        };
     
    	private:
    		int _size_x,_size_y;
    		std::vector< std::vector <T> > _tab;
    };
     
    ///// ------------- Opérateur déclarer à l'extérieur de la classe -------------------
     
    template<typename T>
    inline std::ostream& operator<<(std::ostream &flux, const Tab2D<T> &tab){
      tab.print(flux);
      return flux;
    }
     
    /// Permet de pouvoir ecrire : tab1 + tab2;
    template<typename T>
    Tab2D<T> operator+(Tab2D<T> const& a, Tab2D<T> const& b){
    	Tab2D<T> c(a); //on fait appel au constructeur de copie
    	c+=b;
    	return c;
    };
     
    /// Permet de pouvoir ecrire : tab1 - tab2;
    template<typename T>
    Tab2D<T> operator-(Tab2D<T> const& a, Tab2D<T> const& b){
    	Tab2D<T> c(a); //on fait appel au constructeur de copie
    	c-=b;
    	return c;
    };
     
    /// Permet de pouvoir ecrire : tab1 + nb;
    template<typename T>
    Tab2D<T> operator+(Tab2D<T> const& a, T const& b){
    	Tab2D<T> c(a); //on fait appel au constructeur de copie
    	c+=b;
    	return c;
    };
     
    /// Permet de pouvoir ecrire : tab1 - nb;
    template<typename T>
    Tab2D<T> operator-(Tab2D<T> const& a, T const& b){
    	Tab2D<T> c(a); //on fait appel au constructeur de copie
    	c-=b;
    	return c;
    }; 
     
    /// Pour comparer deux tableaux
    template<typename T>
    bool operator==(Tab2D<T> const& a, Tab2D<T> const& b){
    	return a.estEgal(b,1);
    };
     
     
    #endif // TABLEAU_H_INCLUDED
    Voici maintenant le main :
    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
    #include <iostream>
    #include <vector>
    #include "tableau.h"
     
    int main()
    { 
    	double a(2.3);
     
       Tab2D<double> monTab(4,2);
       monTab=1;
       monTab(2,2)=3.4;
     
       Tab2D<double> monTab2(monTab);
    	monTab2+=a;
     
        std::cout << " monTab     : " << std::endl << monTab << std::endl;
        std::cout << " monTab2    : " << std::endl << monTab2 << std::endl;
     
       	std::cout << " monTab + "<< a <<" == monTab2    =>   " ;
        if (monTab+a==monTab2) {
         std::cout << "true"  << std::endl;
         } else { std::cout << "false"  << std::endl; }
     
       	std::cout << " monTab == monTab2 - "<< a <<"   =>    " ;     
        if (monTab== monTab2-a ) {
        std::cout << "true"  << std::endl;
         } else { std::cout << "false"  << std::endl; }
     
     
       return 0;
    }
    Et là surprise à l'éxécution :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     monTab     : 
    1  1  1  3.4  
    1  1  1  1  
     
     monTab2    : 
    3.3  3.3  3.3  5.7  
    3.3  3.3  3.3  3.3  
     
     monTab + 2.3 == monTab2    =>   true
     monTab == monTab2 - 2.3   =>    Première difference identifiée :
    (3,0) --> 3.4 / 3.4
    false
    Je n'arrive pas à me dépatouiller de ce bug...
    J'ai essayé plusieurs valeurs de a. Il semblerait que le souci n'arrive que si a est positif...

    Si quelqu'un peut m'éclairer.

    Merci à vous

  2. #2
    Membre éclairé Avatar de PadawanDuDelphi
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2006
    Messages
    678
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Bâtiment

    Informations forums :
    Inscription : Août 2006
    Messages : 678
    Points : 717
    Points
    717
    Par défaut
    A première vue, tu as as un problème de comparaison de double.
    Tu as des infos dans la faq C++ à ce sujet.

    ++.
    For crying out loud !

  3. #3
    Membre émérite

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Points : 2 328
    Points
    2 328
    Par défaut
    Oui j'ai déjà visité la FAQ.

    Notamment :
    http://cpp.developpez.com/faq/cpp/?p...ateur-egalegal

    Je ne sais pas si c'est celle-ci que tu avais en tête ?

    Ici dans mon cas j'utilise l'opérateur == sur les doubles, donc la comparaison devrait se faire à précision machine, contrairement à l'exemple donné dans la FAQ ou la précision est fixé volontairement à 0.001 et où on regarde la valeur de la différence.
    Ici dans mon exemple le réel 2.3 ne dépasse bien évidemment pas cette précision ...

    Bon cela dit, j'ai quand même regarder la différence du coup.
    Dans le corps de la fonction EstEgal, en cas de différence, j'ai rajouté cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::cout << "difference : " << _tab[i][j]-b(i,j) << std::endl;
    Et à l'execution, j'obtiens en plus :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    difference : 4.44089e-16
    Le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::cout <<  std::numeric_limits<double>::epsilon() << std::endl;
    me renvoit Donc là je suis frustré. Si je fais 3.4+2.3-2.3 je récupère 3.4+eps où eps supérieur à la précision machine !
    Alors que 1+2.3-2.3 dans les autres cases du tableau me retourne bien 1 !

    J'aurais choisi des nombres avec plein de chiffres après la virgule, qu'il y aurait eu un problème de précision machine : ok.
    Mais là sur un petit nombre comme ça, c'est possible? Ou bien le bug n'est-il pas là ?

  4. #4
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    J'aurais choisi des nombres avec plein de chiffres après la virgule, qu'il y aurait eu un problème de précision machine : ok.
    Mais là sur un petit nombre comme ça, c'est possible? Ou bien le bug n'est-il pas là ?
    Les nombres entiers jusque 2^53 peuvent être représentés sans perte de précision.
    Les nombres à virgule par contre... Même un double ne permet pas de représenter 3.4 (ni 2.3) sans perte de précision.
    En interne c'est en binaire, et en binaire 3.4 est impossible à représenter exactement



    Quand exponent est non nul, la valeur d'un double est donnée par
    (-1)^sign * 2^(exponent - 1023) * 1.fraction
    Regarde d1 par exemple.
    sign = 0
    exponent - 1023 = 1
    fraction = 0xb333333333333

    en binaire : fraction = 1011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011
    c'est égal à 2^(-1) + 2^(-3) + 2^(-4) + 2^(-7) + ...
    = 0.5 + 0.125 + 0.0625 + 0.0078125 + ....
    = 0.6953125 (j'ai pris que les 4 premiers termes, j'avais un peu la flemme de tous les faire ^^)

    au final d1 = (-1)^0 * 2^1 * (1 + 0.6953125)
    d1 = 3.390625

    Tu fais 2 opérations (1 addition et 1 soustraction), donc tu as une erreur de maximum 2 * epsilon.

  5. #5
    Membre émérite

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Points : 2 328
    Points
    2 328
    Par défaut
    Ca veux dire que je dois réécrire mon test qui était (ligne 58 du premier code posté):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if ( b(i,j)!= _tab[i][j]) { // ...
    de sorte à ce qu'il accepte une certaine tolérance.
    Allons-y, alors admettons je prends une erreur relative:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    T eps(std::numeric_limits<T>::epsilon());
    if( abs(b(i,j)-_tab[i][j])/std::max(std::max(b(i,j),_tab[i][j]),eps) > 10*eps ) { // ...
    Dans ce cas, là vraiment pas top alors, car si maintenant j'appelle ma classe avec un tableau d'int... !
    Pour parer, j'ai trouvé ce que je vous présente dans la suite.

    Je modifie ma méthode estEgal de la classe Tab2D, comme suit :

    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
    bool estEgal(Tab2D const& b, int debug=0) const{
    		bool res=false;
    		if (SameSize(b)){
    			res=true;
    			for (int i=0; i<_size_x && res; i++){
    				for (int j=0; j<_size_y && res; j++){
    					res=isEqual(b(i,j),_tab[i][j], debug);
    				}
    			}
    		} else if (debug==1){
    			std::cout << "Tableau qui ne sont pas de même taille :" << std::endl;
    			std::cout <<  _size_x << " "<< _size_y << " / " << b.size(1) << " " << b.size(2) << std::endl;
    		}
    		return res;
    	};
    où la fonction IsEqual est définit au début du fichier par :
    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
     
    #include <assert.h>
    #include <stdlib.h> // pour abs()
    #include <vector>
     
    template <typename T> bool isEqual(T const& a ,T const& b, int debug=0){
    	T eps(std::numeric_limits<T>::epsilon());
    	bool res( abs(b-a)/std::max(std::max(a,b),eps) < 10*eps );
    	//bool res( a==b ); /// <-- Pas bon à cause de la precision machine !
    	if (debug==1 && !res){
    		std::cout << "Première difference identifiée :" <<  a << " / " << b << std::endl;
    		std::cout << "            difference (generique) : " << b-a << std::endl;
    	}
    	return res;
    }
     
    bool isEqual(int a ,int b, int debug=0){
    	bool res( a==b );
    	if (debug==1 && !res)
    		std::cout << "Première difference (int) identifiée :" <<  a << " / " << b << std::endl;
    	return res;
    }
    Donc ici la parade que j'ai utilisé, c'est de définir une fonction extérieure surchargée.
    D'un point de vue de programmeur, c'est acceptable comme parade où vous me conseillez autre chose avant que je mette en résolu ?

    Merci pour votre aide.

    lg53

  6. #6
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    Dans ce cas, là vraiment pas top alors, car si maintenant j'appelle ma classe avec un tableau d'int... !
    Tu peux templatiser et utiliser std::is_integral.

  7. #7
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    J'aurais plutôt laissé le cas général avec == et spécialisé pour le cas particulier des float/double avec le epsilon.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  8. #8
    Membre émérite

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Points : 2 328
    Points
    2 328
    Par défaut
    @oodini :
    Je ne connaissais pas std::is_integral.
    Effectivement ca paraît plus élégant. Ca a quand même l'air dangereux, dans la mesure où ça retourne vrai non pas seulement sur les int, mais aussi sur les char. Donc si je veux un template aussi général que possible, il faudrait que je sois capable de dissocier des int de n'importe quel autre type.

    Dans le même style, j'ai cherché à creusé un peu la question et je suis tombé sur std::numeric_limits<T>::is_integer, qui malheureusement souffre du même trouble.

    @Bousk :
    Effectivement, laisser le cas == en cas général, permet une meilleure généralisation, tu as raison.
    Je n'avais tous simplement pas pensé la chose dans ce sens là, dans la mesure où ce sont les int qui me posait problème au départ.
    Donc du coût je dois doublement surcharger la fonction, et les prototypes seraient :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    template <typename T> bool isEqual(T const& a ,T const& b, int debug=0);
    bool isEqual(float a ,float b, int debug=0);
    bool isEqual(double a ,double b, int debug=0);
    Merci à vous tous, pour l'intérêt que vous avez porté à la résolution de mon problème.

    lg53

  9. #9
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Citation Envoyé par Bousk Voir le message
    J'aurais plutôt laissé le cas général avec == et spécialisé pour le cas particulier des float/double avec le epsilon.
    Tu dois alors faire 2 implémentations (voire 3 si tu ajoutes les long doubles).

  10. #10
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par oodini Voir le message
    Tu dois alors faire 2 implémentations (voire 3 si tu ajoutes les long doubles).
    Bah, même principe is_floating_point.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [C#] Tri d'objet et surcharge d'opérateur
    Par Royd938 dans le forum Windows Forms
    Réponses: 6
    Dernier message: 17/12/2007, 00h26
  2. Petit probléme de surcharge d'opérateur .
    Par Clad3 dans le forum C++
    Réponses: 20
    Dernier message: 11/04/2005, 20h15
  3. Problème de surcharge d'opérateurs
    Par Hell dans le forum C++
    Réponses: 17
    Dernier message: 17/01/2005, 16h01
  4. Cumul de surcharges d'opérateurs
    Par Nats dans le forum C++
    Réponses: 2
    Dernier message: 11/10/2004, 13h37
  5. [VB .NET] Surcharge d'opérateur
    Par Franckintosh dans le forum VB.NET
    Réponses: 2
    Dernier message: 07/09/2004, 19h05

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