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 :

Erreur d'arrondi lors de la conversion double en int16_t


Sujet :

C++

  1. #1
    Membre du Club
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Mars 2006
    Messages
    71
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Allemagne

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique

    Informations forums :
    Inscription : Mars 2006
    Messages : 71
    Points : 63
    Points
    63
    Par défaut Erreur d'arrondi lors de la conversion double en int16_t
    Bonjour,

    le programme suivant converti une valeur décimal en binaire après l'application d'un décalage (offset) et d' un facteur d’échelle (scaling).

    La division par 0.01 dans la fonction float2int introduit une erreur d'arrondi. Pourquoi?

    001001001111 est le résultat obtenu (591 en décimal)
    001001010000 est le résultat attendu (592 en décimal)



    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
    #include <stdio.h>
    #include <iostream>
    #include <iomanip>
    #include <bitset>
     
    int main(void)
    {
    	int16_t float2int(double value);
    	double Temp_dec;
    	int16_t Temp_bin;
     
    	Temp_dec = 255.92;
    	Temp_bin = float2int(Temp_dec);
     
    	// Print expected 12 bit binary for decimal value 592
    	std::cout << "592 (binary): " << std::bitset<12>(592)<< std::endl;
     
        return 0;
    }
     
    int16_t float2int(double value)
    {
    	value = (value - 250.00) / 0.01; // offset = 250 and scaling factor = 0.01
     
    	std::cout << "Result (dec): " << std::fixed << std::setprecision(2) << value << std::endl;
    	std::cout << "Result (binary): " << std::bitset<12>(value)<< std::endl;
        return (int16_t)(value);
    }
    Résultats:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Result (dec): 592.00
    Result (binary): 001001001111
    592 (binary): 001001010000

  2. #2
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut
    Hello,

    Ton problème est mis en évidence par ce code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>
     
    int main() {
    	double d = 255.92;
    	std::cout << d << '\n';
     
    	d = (d - 250.0) / 0.01;
    	std::cout << d << '\n';
     
    	auto i = (int16_t) (d);
    	std::cout << i << '\n';
    }
    Il affiche :

    255.92
    592
    591

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Salut,

    De manière générale, la conversion ("pure et simple" oserais-je écrire) d'un réel (un double, dans ton cas) en entier (un int) va se contenter d'ignorer la partie décimale du réel pour créer l'entier: si le réel vaut 256, l'entier vaudra 256 et ce, que la fraction décimale valle 0.00001 ou 0.999999.

    Autrement dit: il n'y a absolument aucun arrondi qui sera exécuté lors de la conversion

    Si tu veux que les règles d'arrondi soient respectées (arrondi à la valeur inférieure ente 0 et 0.5 exclus et à la valeur supérieure entre 0.5 et 0.0 exclus), il faudra demander explicitement de respecter ces règles à l'aide de la fonction std::round.

    Voici un petit code qui te montre ce qui se passe
    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
    #include <iostream>
    #include <iomanip>
    #include <cmath>
     
    int main(){
        double first{256.4999999};
    	double second{256.5000001};
    	int firstUnrounded{first};
    	int firstRounded{std::round(first)};
    	int secondUnrounded{second};
    	int secondRounded{std::round(second)};
    	std::cout<<std::setprecision(10)<<"unrounded "<<first<<" : "<<firstUnrounded<<"\n"
    	         <<"rounded "<<first<<" : "<<firstRounded<<"\n"
    			 <<"unrounded "<<second<<" : "<<secondUnrounded<<"\n"
    			 <<"rounded "<<second<<" : "<<secondRounded<<"\n";
    }
    et qui va afficher
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    a.exe
    unrounded 256.4999999 : 256
    rounded 256.4999999 : 256
    unrounded 256.5000001 : 256
    rounded 256.5000001 : 257
    Après, il est aussi possible de jouer avec std::ceil, std::floor et std::trunc (comportement "par défaut"), en fonction de ce qui t'intéresse à un moment donné
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    Membre éprouvé
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    562
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 94
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 562
    Points : 1 253
    Points
    1 253
    Par défaut
    Salut,

    On peut aussi essayer de récupérer la partie significative de la température avant qu'elle ne soit altérée, en changeant l'ordre des opérations par exemple: (temp*100-25000), ou en travaillant au plus tôt avec des entiers, mais là ça dépend de la platforme cible sauf bien sûr s'il s'agit de sniffer un bus CAN sur PC.

  5. #5
    Membre du Club
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Mars 2006
    Messages
    71
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Allemagne

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique

    Informations forums :
    Inscription : Mars 2006
    Messages : 71
    Points : 63
    Points
    63
    Par défaut
    Citation Envoyé par koala01 Voir le message
    De manière générale, la conversion ("pure et simple" oserais-je écrire) d'un réel (un double, dans ton cas) en entier (un int) va se contenter d'ignorer la partie décimale du réel pour créer l'entier: si le réel vaut 256, l'entier vaudra 256 et ce, que la fraction décimale valle 0.00001 ou 0.999999.
    Je comprends ton explication, mais dans mon cas le résultat de l'opération de soustraction est fini et celui de la division devrait être un nombre réel entier.

    j' effectue les operation suivante en utilisant le type "double"
    (255.92 - 250.00) = 5.92
    5.92/ 0.01 = 592
    et seulement après l’opération de division, je convertis ce résultat en int16_t et une erreur d'arrondi apparaît (591)

    Il est vrai que lorsque j'utilise une multiplication en place de la division, l'erreur d'arrondi disparait.
    5.92*100 = 592
    Le résultat en int16_t est exact et égal a 592

    Y-a-t-il une fonction ou méthode pour gérer les arrondis facilement lors du passage d'une variable de type double a un type int?
    (pas dans l'affichage cout ou printf mais bien dans la variable)

    Merci beaucoup pour votre aide et conseils

  6. #6
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 562
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 562
    Points : 7 628
    Points
    7 628
    Par défaut
    Citation Envoyé par minogttao Voir le message
    Y-a-t-il une fonction ou méthode pour gérer les arrondis facilement lors du passage d'une variable de type double a un type int?
    (pas dans l'affichage cout ou printf mais bien dans la variable)
    Pour arrondir à l'entier le plus proche, il faut utiliser les fonctions d'arrondi comme déjà dit.
    On a la fonction lround() qui retourne directement un long ou bien llround() qui retourne un long long.

  7. #7
    Expert confirmé

    Homme Profil pro
    Directeur de projet
    Inscrit en
    Mai 2013
    Messages
    1 317
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Service public

    Informations forums :
    Inscription : Mai 2013
    Messages : 1 317
    Points : 4 124
    Points
    4 124
    Par défaut Div ou not div...
    Bonjour,

    Quand c'est possible, la multiplication est toujours préférable à la division :
    • Le diviseur doit être exact en binaire si on ne veut pas avoir d'emblée une valeur approchée. Or 1/100 = 1/2²5² et 5 est premier avec 2 donc la représentation sera approchée.
    • Indépendamment du point précédent, si la division ne tombe pas juste, le résultat n'est qu'approché : 1/3 = 0.333333333... mais les nombres flottants n'ont pas une précision infinie.
    • La division est environ 3 fois plus lente que la multiplication.


    Aussi, la proposition de kaitlyn d'utiliser temp*100-25000 ou, si on souhaite rester en flottant, temp*100.0-25000.0 me semble nettement préférable.

    Salutations
    Ever tried. Ever failed. No matter. Try Again. Fail again. Fail better. (Samuel Beckett)

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Citation Envoyé par minogttao Voir le message
    Je comprends ton explication, mais dans mon cas le résultat de l'opération de soustraction est fini et celui de la division devrait être un nombre réel entier.

    j' effectue les operation suivante en utilisant le type "double"
    (255.92 - 250.00) = 5.92
    5.92/ 0.01 = 592
    et seulement après l’opération de division, je convertis ce résultat en int16_t et une erreur d'arrondi apparaît (591)

    Il est vrai que lorsque j'utilise une multiplication en place de la division, l'erreur d'arrondi disparait.
    5.92*100 = 592
    Le résultat en int16_t est exact et égal a 592
    Ca, c'est à cause de l'imprécision de la représentation des valeurs réelles (en binaire, ici, bien que l'on retrouve le même problème en décimal : 1/3 = 0.333333....)

    Dés que tu effectue un premier calcul avec ton réel (255.92 - 255.0), tu peux avoir "une certaine imprécision" dans le résultat, qui pourrait tout aussi bien être représenté (sous une forme "finie") par la valeur 5.92000001 ou par la valeur .... 5.919999998.

    (Dans ton cas, il semblerait que ce soit la deuxième solution )

    Et comme les réels sont représentés sous une forme "scientifique" (mantisse + exposant), le deuxième calcul (resultat / 0.01) va "tout simplement" modifier l'exposant pour y ajouter 2, ce qui va faire passer la représentation en mémoire de 5.9199998 ^ 0 à 5.9199998 ^ 2, soit... 591,999998.

    Et comme la partie décimale sera purement et simplement ignorée à moins d'obliger l'arrondi, tu te retrouves exactement dans la situation que je décrivais plus haut

    D'ailleurs, si tu forçais la précision de l'affichage de value à "un certain nombre de chiffres" (mettons 6) après la virgule, tu t'en rendrais sans doute beaucoup plus facilement compte
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Erreur de syntaxe lors de la conversion d'une valeur datetime
    Par info3licen dans le forum Débuter avec Java
    Réponses: 10
    Dernier message: 28/05/2011, 02h31
  2. [Free Pascal] Erreur exitcode 217 lors de la conversion d'une image en tableau
    Par _Hope_ dans le forum Free Pascal
    Réponses: 7
    Dernier message: 18/05/2009, 21h54
  3. Conversion : erreur d'arrondi plutôt désagréable.
    Par NicolasJolet dans le forum Framework .NET
    Réponses: 2
    Dernier message: 01/02/2007, 22h39
  4. Erreur lors de la conversion de MyISSAM vers InnoDB
    Par faico dans le forum Requêtes
    Réponses: 12
    Dernier message: 26/08/2006, 23h25
  5. Réponses: 2
    Dernier message: 21/06/2004, 16h55

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