2017-06-26 3 views
0

J'ai un code C++ ci-dessous,Comment éviter les problèmes de précision en C++ en utilisant des doubles et doubles variables?

#include <iostream> 
#include <cstdio> 
#include <math.h> 
using namespace std; 

int main() 
{ 
    unsigned long long dec,len; 
    long double dbl; 

    while (cin >> dec) 
    { 
     len = log10(dec)+1; 
     dbl = (long double) (dec); 

     while (len--) 
      dbl /= 10.0; 

     dbl += 1e-9; 

     printf ("%llu in int = %.20Lf in long double. :)\n",dec,dbl); 
    } 

    return 0; 
} 

Dans ce code, je voulais convertir un entier en un nombre à virgule flottante. Mais pour certaines entrées, il a donné quelques erreurs de précision. J'ai donc ajouté 1e-9 avant d'imprimer le résultat. Mais encore, il montre des erreurs pour toutes les entrées, en fait j'ai quelques chiffres supplémentaires dans le résultat. Certains d'entre eux sont donnés ci-dessous,

stdin 
1 
12 
123 
1234 
12345 
123456 
1234567 
12345678 
123456789 
1234567890 

stdout 
1 in int = 0.10000000100000000000 in long double. :) 
12 in int = 0.12000000100000000001 in long double. :) 
123 in int = 0.12300000100000000000 in long double. :) 
1234 in int = 0.12340000100000000000 in long double. :) 
12345 in int = 0.12345000099999999999 in long double. :) 
123456 in int = 0.12345600100000000000 in long double. :) 
1234567 in int = 0.12345670100000000000 in long double. :) 
12345678 in int = 0.12345678099999999998 in long double. :) 
123456789 in int = 0.12345679000000000001 in long double. :) 
1234567890 in int = 0.12345679000000000001 in long double. :) 

Y at-il un moyen d'éviter ou de se débarrasser de ces erreurs? :)

+0

Bienvenue dans le monde merveilleux de l'arithmétique en virgule flottante (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). – tadman

+0

Quel est votre véritable objectif? Qu'est-ce que vous essayez de faire avec ces chiffres où un [ULP (unité à la dernière place)] (https://en.wikipedia.org/wiki/Unit_in_the_last_place) fait la différence? –

Répondre

2

Non, il n'y a pas moyen de contourner cela. Un nombre à virgule flottante est fondamentalement une fraction avec une puissance de 2 comme dénominateur. Cela signifie que les seuls non-entiers qui peuvent être représentés exactement sont des multiples d'une puissance (négative) de 2, soit un multiple de 1/2, ou de 1/16, ou de 1/1048576, ou ...

Maintenant, 10 a deux facteurs premiers; 2 et 5. Donc 1/10 ne peut pas être exprimé comme un nombre fractionnaire avec une puissance de 2 comme dénominateur. Vous finirez toujours avec une erreur d'arrondi. En divisant à plusieurs reprises par 10, vous faites ce même légèrement pire, si une « solution » serait plutôt que de diviser par 10 dbl gardant à plusieurs reprises un compteur séparé multiplier:

double multiplier = 1; 
while (len--) 
    multiplier *= 10.; 

dbl /= multiplier; 

Notez que je ne dis pas que cela résoudre le problème, mais cela pourrait rendre les choses légèrement plus stables. En supposant que vous pouvez représenter un nombre décimal exactement en virgule flottante reste faux.

+0

avec reconnaissance, il fonctionne jusqu'aux 20 premiers chiffres après la virgule décimale :) ... mais pourquoi vous avez nié dire la serviabilité de cette approche? –

+1

C'est juste pour bien montrer que vous n'avez pas une représentation correcte, juste une qui est moins fausse. Le fait est que si vous avez vraiment besoin d'une représentation "exacte" (décimale), vous devriez utiliser quelque chose comme des mathématiques à virgule fixe de base 10, ou des virgules décimales à codage binaire. Cependant, cette solution vous fournit la meilleure représentation de l'entier original qui est possible dans le système à virgule flottante. Parfois, cela suffit. – cnettel