65 votes

Le formatage double pour la sortie en C #

L'exécution d'une expérience rapide liées à la double Multiplication Cassé dans .NET? et la lecture de quelques articles sur le C# mise en forme de chaîne, j'ai pensé que cela:

{
    double i = 10 * 0.69;
    Console.WriteLine(i);
    Console.WriteLine(String.Format("  {0:F20}", i));
    Console.WriteLine(String.Format("+ {0:F20}", 6.9 - i));
    Console.WriteLine(String.Format("= {0:F20}", 6.9));
}

Serait le C# équivalent de ce code C:

{
    double i = 10 * 0.69;

    printf ( "%f\n", i );
    printf ( "  %.20f\n", i );
    printf ( "+ %.20f\n", 6.9 - i );
    printf ( "= %.20f\n", 6.9 );
}

Cependant, le C# produit de la sortie:

6.9
  6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000

en dépit de je en montrant égale à la valeur 6.89999999999999946709 (plutôt que de 6.9) dans le débogueur.

par rapport à C, ce qui montre la précision demandée par le format:

6.900000                          
  6.89999999999999946709          
+ 0.00000000000000088818          
= 6.90000000000000035527

Ce qui se passe?

( Microsoft .NET Framework Version 3.51 SP1 / Visual Studio C# 2008 Express Edition )


J'ai une formation en calcul numérique et de l'expérience de mise en œuvre de l'arithmétique d'intervalle - une technique pour estimer les erreurs dues à la limite de la précision, compliqué numérique de systèmes sur différentes plates-formes. Pour obtenir la prime, ne pas essayer de l'expliquer sur le stockage de précision - dans ce cas, c'est une différence de un ULP de 64 bits en double.

Pour obtenir la prime, je veux savoir comment (ou si) .Net permet de formater un double de la demande de précision visible dans le code C.

68voto

LukeH Points 110965

Le problème, c'est que .NET sera toujours autour de l' double à 15 chiffres décimaux significatifs avant l'application de votre mise en forme, quelle que soit la précision demandée par votre format et quel que soit l'exacte valeur décimale du nombre binaire.

Je suppose que le débogueur Visual Studio a son propre format/routines d'affichage qui ont directement accès à l'interne nombre binaire, d'où les divergences entre votre code C#, C ton code et le débogueur.

Il n'y a rien intégré qui vous permettra d'accéder à l'exacte valeur décimale d'un double, ou pour vous permettre de formater un double à un nombre de décimales, mais vous pourriez le faire vous-même par démolir l'interne nombre binaire et de le reconstruire comme une représentation de chaîne de la valeur décimale.

Sinon, vous pouvez utiliser Jon Skeet l' DoubleConverter classe (liés à de son "Binaire à virgule flottante et .NET" de l'article). Cela a un ToExactString méthode qui renvoie l'exacte valeur décimale d'un double. Vous pouvez facilement modifier cette option pour permettre à l'arrondissement de la sortie d'une précision spécifique.

double i = 10 * 0.69;
Console.WriteLine(DoubleConverter.ToExactString(i));
Console.WriteLine(DoubleConverter.ToExactString(6.9 - i));
Console.WriteLine(DoubleConverter.ToExactString(6.9));

// 6.89999999999999946709294817992486059665679931640625
// 0.00000000000000088817841970012523233890533447265625
// 6.9000000000000003552713678800500929355621337890625

24voto

Amit Ranjan Points 5241
Digits after decimal point
// just two decimal places
String.Format("{0:0.00}", 123.4567);      // "123.46"
String.Format("{0:0.00}", 123.4);         // "123.40"
String.Format("{0:0.00}", 123.0);         // "123.00"

// max. two decimal places
String.Format("{0:0.##}", 123.4567);      // "123.46"
String.Format("{0:0.##}", 123.4);         // "123.4"
String.Format("{0:0.##}", 123.0);         // "123"
// at least two digits before decimal point
String.Format("{0:00.0}", 123.4567);      // "123.5"
String.Format("{0:00.0}", 23.4567);       // "23.5"
String.Format("{0:00.0}", 3.4567);        // "03.5"
String.Format("{0:00.0}", -3.4567);       // "-03.5"

Thousands separator
String.Format("{0:0,0.0}", 12345.67);     // "12,345.7"
String.Format("{0:0,0}", 12345.67);       // "12,346"

Zero
Following code shows how can be formatted a zero (of double type).

String.Format("{0:0.0}", 0.0);            // "0.0"
String.Format("{0:0.#}", 0.0);            // "0"
String.Format("{0:#.0}", 0.0);            // ".0"
String.Format("{0:#.#}", 0.0);            // ""

Align numbers with spaces
String.Format("{0,10:0.0}", 123.4567);    // "     123.5"
String.Format("{0,-10:0.0}", 123.4567);   // "123.5     "
String.Format("{0,10:0.0}", -123.4567);   // "    -123.5"
String.Format("{0,-10:0.0}", -123.4567);  // "-123.5    "

Custom formatting for negative numbers and zero
String.Format("{0:0.00;minus 0.00;zero}", 123.4567);   // "123.46"
String.Format("{0:0.00;minus 0.00;zero}", -123.4567);  // "minus 123.46"
String.Format("{0:0.00;minus 0.00;zero}", 0.0);        // "zero"

Some funny examples
String.Format("{0:my number is 0.0}", 12.3);   // "my number is 12.3"
String.Format("{0:0aaa.bbb0}", 12.3);

10voto

Timothy Walters Points 8222

Jetez un oeil à cette référence MSDN. Dans les notes, il stipule que les nombres sont arrondis au nombre de décimales demandé.

En revanche, si vous utilisez "{0:R}" il va produire ce qui est appelé un "aller-retour" de la valeur, jetez un oeil à cette référence MSDN pour plus d'info, voici mon code et la sortie:

double d = 10 * 0.69;
Console.WriteLine("  {0:R}", d);
Console.WriteLine("+ {0:F20}", 6.9 - d);
Console.WriteLine("= {0:F20}", 6.9);

sortie

  6.8999999999999995
+ 0.00000000000000088818
= 6.90000000000000000000

8voto

Abel Points 24335

Si cette question est entre-temps fermé, je crois qu'il vaut la peine de mentionner comment cette atrocité est entré en existence. Dans un sens, on peut reprocher à la C# spec, qui stipule qu'un double doit avoir une précision de 15 ou 16 chiffres (le résultat de l'IEEE-754). Un peu plus loin (section 4.1.6), il est indiqué que les implémentations sont autorisés à utiliser plus de précision. Rappelez-vous: plus, pas moins. Ils sont même autorisés à s'écarter de la norme IEEE-754: les expressions de type x * y / zx * y donnerait +/-INF mais serait dans une plage valide après la division, n'ont pas à provoquer une erreur. Cette caractéristique le rend plus facile pour les compilateurs d'utiliser une plus grande précision dans les architectures où qui me donnent de meilleures performances.

Mais j'ai promis une "raison". Voici une citation (qui vous a demandé une ressource dans l'un de vos récents commentaires) de la Source Partagée de la CLI, en clr/src/vm/comnumber.cpp:

"Pour donner de chiffres qui sont à la fois amicale pour l'affichage et l' rond-trippable, nous analyser le nombre à l'aide de 15 chiffres et ensuite déterminer si l' il allers-retours à la même valeur. Si il ne nous convertir ce NOMBRE à un chaîne, sinon nous d'analyse à l'aide de 17 chiffres et l'affichage."

En d'autres termes: les états membres de la CLI de l'Équipe de Développement a décidé d'être à la fois rond-trippable et de montrer assez de valeurs qui ne sont pas une douleur à lire. Bon ou mauvais? Je souhaiterais avoir un opt-in ou opt-out.

Le truc qu'il fait pour trouver ce round-trippability d'un nombre donné? La Conversion à un générique de NUMÉRO de structure (qui a des champs séparés pour les propriétés d'un double) et à l'arrière, puis de comparer si le résultat est différent. Si elle est différente, la valeur exacte est utilisé (comme dans votre milieu valeur 6.9 - i) si c'est le même, le "assez de valeur" est utilisé.

Comme vous l'avez déjà fait remarquer dans un commentaire à Andyp, 6.90...00 est au niveau du bit égal à 6.89...9467. Et maintenant vous savez pourquoi 0.0...8818 est utilisé: c'est au niveau du bit différent de 0.0.

Ce 15 chiffres barrière est codé en dur et ne peut être modifié par la recompilation de la CLI, en utilisant Mono ou en appelant Microsoft et de les convaincre d'ajouter une option pour imprimer des "précision" (il n'est pas vraiment de précision, mais par la faute d'un meilleur mot). Il est probablement plus facile de calculer les 52 bits de précision vous-même ou utiliser la bibliothèque mentionné plus tôt.

EDIT: si vous voulez expérimenter vous-même avec de l'IEE-754 floating points, considérer cet outil en ligne, qui vous montre toutes les parties pertinentes d'un point flottant.

6voto

zwi Points 419

Utilisation

Console.WriteLine(String.Format("  {0:G17}", i));

Qui vous donnera toutes les 17 chiffres qu'il ont. Par défaut, une valeur Double contient 15 décimales de précision, bien qu'un maximum de 17 chiffres est conservé en interne. {0:R} ne sera pas toujours vous donner 17 chiffres, il va donner à 15 si le nombre peut être représenté avec précision.

qui renvoie à 15 chiffres si le nombre peut être représenté avec précision ou 17 chiffres si le nombre ne peut être représenté avec un maximum de précision. Il n'y a pas quelque chose que vous pouvez faire pour rendre le double de retour plus de chiffres qui est la façon dont il est mis en œuvre. Si vous ne l'aimez pas faire un nouveau double classe vous-même...

.NET est le double ne le stocker plus de chiffres que 17 si vous ne pouvez pas voir 6.89999999999999946709 dans le débogueur, vous verriez 6.8999999999999995. Veuillez fournir une image de prouver que nous avons tort.

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