35 votes

Impression double sans perte de précision

Alors comment faire pour imprimer un double à un cours d'eau de sorte que quand il est lu, vous ne perdez pas de précision?

J'ai essayé:

std::stringstream ss;

double v = 0.1 * 0.1;
ss << std::setprecision(std::numeric_limits<T>::digits10) << v << " ";

double u;
ss >> u;
std::cout << "precision " << ((u == v) ? "retained" : "lost") << std::endl;

Cela n'a pas fonctionné comme je l'espérais.

Mais je peux augmenter la précision (ce qui m'a surpris car je pensais que digits10 était le max requis).

ss << std::setprecision(std::numeric_limits<T>::digits10 + 2) << v << " ";
                                                 //    ^^^^^^ +2

Ainsi, il a à voir avec le nombre de chiffres significatifs et les 2 premiers ne comptent pas dans (0.01).

Donc, personne n'a cherché à représenter des nombres à virgule flottante exactement? Quelle est l'exacte incantation magique sur le stream que je dois faire?

Edit:

Après une période d'expérimentation.

Le problème était avec ma version d'origine, il y avait non de chiffres significatifs dans la chaîne de caractères après la virgule qui a affecté la précision.

Donc, pour compenser cela, nous pouvons utiliser la notation scientifique pour compenser:

ss << std::scientific 
   << std::setprecision(std::numeric_limits<double>::digits10 + 1)
   << v;

Ce n'est toujours pas expliquer la nécessité pour le +1 si.

Aussi, si j'ai imprimer le nombre avec plus de précision je obtenir plus de précision imprimé!

std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10) << v << "\n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v << "\n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits) << v << "\n";

Résultats:

1.000000000000000e-02
1.0000000000000002e-02
1.00000000000000019428902930940239457413554200000000000e-02

Edit 2

Basé sur @Stephen Canon réponse ci-dessous:

Nous pouvons imprimer exactement par uisng le printf() formateur de table "%a" ou "%A". Pour atteindre cet en C++, nous devons utiliser les fixes et scientifiques des manipulateurs (voir n3225: 22.4.2.2.2p5 Tableau 88)

std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
std::cout << v;

Pour l'instant j'ai défini:

template<typename T>
std::ostream& precise(std::ostream& stream)
{
    std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
    return stream;
}

std::ostream& preciselngd(std::ostream& stream){ return precise<long double>(stream);}
std::ostream& precisedbl(std::ostream& stream) { return precise<double>(stream);}
std::ostream& preciseflt(std::ostream& stream) { return precise<float>(stream);}

Prochaine. Comment gérons-nous NaN/Inf

17voto

DigitalRoss Points 80400

ce n'est pas correct de dire "à virgule flottante est inexact", même si j'avoue que c'est une simplification utile. Si nous avons utilisé la base de 8 ou 16 dans la vraie vie, alors les gens d'ici est-à-dire "de la base de 10 fraction décimale paquets sont inexactes, pourquoi personne n'a jamais cuire ceux-ci?".

Le problème est que partie intégrante des valeurs de traduire exactement d'une base dans une autre, mais des valeurs fractionnaires ne sont pas, parce qu'ils représentent des fractions de l'intégrale de l'étape et seulement quelques-uns d'entre eux sont utilisés.

Arithmétique à virgule flottante est techniquement tout à fait précis. Chaque calcul a un et un seul résultat possible. Il est un problème, c'est que la plupart des fractions décimales ont en base 2 représentations qui se répètent. En fait, dans l'ordre de 0,01, 0,02, de ... 0.99, à seulement 3 valeurs exactes des représentations binaires. (0.25, 0.50 et 0.75.) Il y a 96 valeurs qui se répètent et, par conséquent, ne sont évidemment pas exactement.

Maintenant, il ya un certain nombre de façons d'écrire et de lire des nombres à virgule flottante sans perdre un seul bit. L'idée est d'éviter d'essayer d'exprimer le nombre binaire avec une base de 10 fraction.

  • Écrire en binaire. Ces jours-ci, tout le monde met en œuvre le format IEEE-754 tant et aussi longtemps que vous choisissez d'ordre des octets et en écriture ou en lecture seule, que l'ordre des octets, puis les numéros de portable.
  • De les écrire en entier 64 bits des valeurs. Ici, vous pouvez utiliser les propriétés de la base 10. (Parce que vous êtes représentant de l'64 bits alias entier, pas les 52 bits fraction.)

Vous pouvez aussi écrire plus de fraction décimale chiffres. Si c'est peu-à-peu précis dépendra de la qualité de la conversion des bibliothèques et je ne suis pas sûr que je serais compter sur une parfaite précision (à partir du logiciel) ici. Mais toutes les erreurs seront extrêmement petit et vos données d'origine n'a certainement aucune information dans le les bits de poids faible. (Aucun des constantes de la physique et de la chimie sont connus pour 52 bits, ni a n'importe quelle distance, sur la terre jamais été mesurée à 52 bits de précision.) Mais pour une sauvegarde ou une restauration où le bit à bit de précision que l'on peut comparer automatiquement, ce n'est évidemment pas l'idéal.

14voto

Stephen Canon Points 58003

Ne pas imprimer les valeurs à virgule flottante en décimal si vous ne voulez pas perdre de précision. Même si vous l'imprimez assez de chiffres pour représenter le nombre exactement, pas toutes les implémentations d'avoir correctement arrondies conversions vers/à partir de décimal chaînes sur l'ensemble de la virgule flottante, de sorte que vous pouvez toujours perdre de la précision.

Utiliser hexadécimal à virgule flottante à la place. En C:

printf("%a\n", yourNumber);

C++0x fournit l' hexfloat manipulateur pour iostreams qui fait la même chose (sur certaines plates-formes, à l'aide de l' std::hex modificateur a le même résultat, mais ce n'est pas un portable hypothèse).

À l'aide de hex à virgule flottante est préféré pour plusieurs raisons.

Tout d'abord, les imprimés de valeur est toujours exacte. Aucun arrondi dans l'écriture ou la lecture d'une valeur formaté de cette manière. Au-delà de l'exactitude des avantages, cela signifie que la lecture et l'écriture de telles valeurs peuvent être plus rapides sur un bien à l'écoute io bibliothèque. Ils ont aussi besoin de moins de chiffres pour représenter les valeurs exactement.

11voto

Yale Zhang Points 344

Je me suis intéressé à cette question, parce que je suis en train de (de)sérialiser mes données vers et à partir de JSON.

Je pense avoir une explication plus claire (avec moins de main levee) pourquoi les 17 chiffres décimaux sont suffisantes pour permettre la reconstitution du nombre d'origine sans perte:

enter image description here

Imaginez 3 nombre de lignes:
1. pour la base d'origine 2 nombre
2. pour l'arrondi de la base de 10 représentation
3. pour la reconstitution du nombre (de même que le n ° 1 parce que les deux en base 2)

Lors de la conversion de la base 10, graphiquement, à vous de choisir la tic sur le 2ème numéro de la ligne la plus proche de la tic sur le 1er. De même, lorsque vous reconstruire l'original de l'arrondi de la base 10 de la valeur.

L'observation critique que j'ai eu était que, pour permettre la reconstruction exacte, la base 10 taille du pas (quantum) doit être < à la base 2 quantique. Sinon, vous inévitablement la mauvaise reconstruction indiqués en rouge.

Prenons le cas particulier d'lorsque l'exposant est 0 pour la base2 représentation. Puis la base2 quantique sera 2^-52 ~= 2.22 * 10^-16. La base la plus proche de 10 quantique, c'est moins que c'est 10^-16. Maintenant que nous connaissons la base requise de 10 quantique, combien de chiffres sont nécessaires pour coder toutes les valeurs possibles? Étant donné que nous ne sommes qu'à considérer le cas de l'exposant = 0, la gamme dynamique de valeurs dont nous avons besoin pour représenter est [1.0, 2.0). Par conséquent, les 17 chiffres seraient nécessaires (16 chiffres pour la fraction et 1 chiffre pour la partie entière).

Pour les exposants autre que 0, on peut utiliser la même logique:

 exposant base2 quant. base10 quant. gamme dynamique chiffres nécessaires
---------------------------------------------------------------------
 1 2^-51 10^-16 [2, 4) 17
 2 2^-50 10^-16 [4, 8) 17
 3 2^-49 10^-15 [8, 16) 17
...
 32 2^-20 10^-7 [2^32, 2^33) 17
 1022 9.98e291 1.0e291 [4.49e307,8.99e307) 17

Bien que non exhaustive, le tableau montre la tendance à 17 chiffres sont suffisants.

J'espère que vous aimez mon explication.

7voto

ThomasMcLeod Points 2717

Un double a la précision de 52 chiffres binaires ou 15.95 chiffres après la virgule. Voir http://en.wikipedia.org/wiki/IEEE_754-2008. Vous devez avoir au moins 16 chiffres décimaux à enregistrer la totalité de la précision d'un double dans tous les cas. [Mais voir la quatrième édition, ci-dessous].

Par ailleurs, ce moyen de chiffres significatifs.

Répondre à l'OP modifications:

Votre virgule flottante à une chaîne décimale d'exécution est outputing façon plus de chiffres sont significatifs. Un double ne peut contenir 52 bits de significande (en fait, 53, si vous comptez un "caché" 1 qui n'est pas stockée). Cela signifie que la résolution n'est pas plus de 2 ^ -53 = 1.11 e-16.

Par exemple: 1 + 2 ^ -52 = 1.0000000000000002220446049250313 . . . .

Ces chiffres décimaux, .0000000000000002220446049250313 . . . . sont les plus petits binaire "étape" dans un double lors de la conversion de décimal.

Le "étape" à l'intérieur de la double est:

.0000000000000000000000000000000000000000000000000001 en binaire.

Notez que le binaire étape est exacte, alors que la virgule étape est inexact.

D'où la représentation décimale ci-dessus,

1.0000000000000002220446049250313 . . .

est une représentation inexacte de l'exact nombre binaire:

1.0000000000000000000000000000000000000000000000000001.

Troisième Édition:

La prochaine valeur possible pour un double, qui en binaire exacte est:

1.0000000000000000000000000000000000000000000000000010

convertit inexactly en décimal à

1.0000000000000004440892098500626 . . . .

De sorte que tous les chiffres dans les décimales ne sont pas vraiment significatifs, ils sont juste à la base de la conversion des artefacts.

Quatrième Édition:

Si un double des magasins de plus de 16 chiffres décimaux significatifs, parfois 17 chiffres décimaux sont nécessaires pour représenter le nombre. La raison a à voir avec les chiffres de découpage.

Comme je l'ai mentionné ci-dessus, il y a 52 + 1 chiffres binaires dans le double. Le "+ 1" est une hypothèse de leader de 1, et ne sont ni stockées ni significative. Dans le cas d'un nombre entier, 52 chiffres binaires forme d'un nombre compris entre 0 et 2^53 - 1. Combien de chiffres après la virgule sont nécessaires pour stocker un tel nombre? Eh bien, log_10 (2^53 - 1) est d'environ 15.95. Donc, à plus de 16 chiffres décimaux sont nécessaires. Laissez l'étiquette de ces d_0 à d_15.

Considérons maintenant que la norme IEEE nombres à virgule flottante ont également un binaire de l'exposant. Ce qui se passe quand on incrémente le exponet par, disons, les 2? Nous avons multiplié nos 52-nombre de bits, quel qu'il fut, par 4. Maintenant, au lieu de nos 52 chiffres binaires en alignant parfaitement avec nos chiffres décimaux d_0 à d_15, nous avons d'importants chiffres binaires représentés dans d_16. Cependant, depuis que nous avons multiplié par quelque chose de moins de 10 personnes, nous ont toujours des chiffres binaires représentés dans d_0. Donc, notre 15.95 chiffres décimaux maintenant occuply d_1 à d_15, en plus de certains bits de poids de d_0 et certains bits de poids faible de d_16. C'est pourquoi, 17 chiffres décimaux sont parfois nécessaires pour représenter un IEEE double.

Cinquième Édition

Fixe des erreurs numériques

4voto

dan04 Points 33306

La façon la plus simple (pour la norme IEEE 754 double) pour garantir un aller-retour de conversion est de toujours utiliser les 17 chiffres significatifs. Mais qui a l'inconvénient de parfois, y compris le bruit inutile chiffres (de 0,1 → "0.10000000000000001").

Une approche qui a fonctionné pour moi est-à - sprintf le nombre avec une précision de 15 chiffres, puis vérifier si atof vous donne en retour la valeur d'origine. Si ça ne marche pas, essayez de 16 chiffres. Si qui ne fonctionne pas, utiliser 17.

Vous voudrez peut-être essayer David Gay de l'algorithme (utilisé en Python 3.1 à mettre en oeuvre float.__repr__).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X