Question

Exécution d'un test rapide relatif à la double multiplication est-elle rompue dans .NET? et en lisant quelques articles sur le formatage de chaîne C #, je pensais que ceci:

{
    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 l'équivalent C # 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 la sortie:

6.9
  6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000

bien que la valeur affichée soit égale à 6,8999999999999999946709 (plutôt que 6,9) dans le débogueur.

comparé à C qui montre la précision demandée par le format:

6.900000                          
  6.89999999999999946709          
+ 0.00000000000000088818          
= 6.90000000000000035527          

Que se passe-t-il?

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

J'ai une formation en calcul numérique et une expérience de la mise en œuvre de l'arithmétique par intervalles, une technique permettant d'estimer les erreurs dues aux limites de précision dans les systèmes numériques complexes, sur diverses plates-formes. Pour obtenir la prime, n'essayez pas d'expliquer la précision du stockage - dans ce cas, il s'agit de la différence d'un ULP d'un double 64 bits.

Pour obtenir la prime, je veux savoir comment. Net peut formater un double de la précision demandée, comme indiqué dans le code C.

Était-ce utile?

La solution

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

Je suppose que le débogueur Visual Studio a ses propres routines de format / d'affichage qui accèdent directement au nombre binaire interne, d'où les différences entre votre code C #, votre code C et le débogueur.

Aucun élément intégré ne vous permet d'accéder à la valeur décimale exacte d'un double , ou de vous permettre de formater un double en un nombre spécifique de décimales Vous pouvez le faire vous-même en séparant le nombre binaire interne et en le reconstruisant sous forme de chaîne représentant la valeur décimale.

Vous pouvez également utiliser la classe DoubleConverter de Jon Skeet (lien lié). à partir de son "Article à virgule flottante binaire et .NET" ). Cela a une méthode ToExactString qui renvoie la valeur décimale exacte d'un double . Vous pouvez facilement le modifier pour activer l’arrondi de la sortie avec 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

Autres conseils

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);

Consultez cette référence MSDN . Dans les notes, il est indiqué que les nombres sont arrondis au nombre de décimales demandées.

Si, à la place, vous utilisez " {0: R} " il produira ce que l'on appelle un "aller-retour". valeur, consultez cette référence MSDN pour plus d'informations, 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

Bien que cette question soit entre-temps close, je pense qu’il est utile de mentionner comment cette atrocité a été créée. D'une certaine manière, vous pouvez blâmer la spécification C #, qui stipule qu'un double doit avoir une précision de 15 ou 16 chiffres (résultat de IEEE-754). Un peu plus loin (section 4.1.6), il est indiqué que les implémentations sont autorisées à utiliser la précision supérieure . Remarquez: plus haut , pas plus bas. Ils sont même autorisés à s'écarter de IEEE-754: expressions du type x * y / z x * y donnerait +/- INF mais serait dans une plage valide après la division, ne doivent pas entraîner d'erreur. Cette fonctionnalité permet aux compilateurs d’utiliser plus facilement la précision dans les architectures où cela donnerait de meilleures performances.

Mais j’ai promis une "raison". Voici une citation (vous avez demandé une ressource dans l'un de vos commentaires récents) à partir du CLI de source partagée , dans clr / src / vm / comnumber.cpp :

  

"Pour donner des nombres qui sont à la fois   convivial à afficher et   rond-trippable, nous analysons le nombre   en utilisant 15 chiffres et ensuite déterminer si   il va et vient à la même valeur. Si   il convertit NUMBER en un   chaîne, sinon on répare en utilisant 17   chiffres et l’afficher. "

En d'autres termes: l'équipe de développement CLI de MS a décidé d'être à la fois round-trippable et de montrer de jolies valeurs qui ne sont pas si pénibles à lire. Bon ou Mauvais? Je souhaite un opt-in ou opt-out.

Quel est l’astuce pour découvrir cette possibilité de tripler un nombre donné? Conversion en une structure NUMBER générique (comportant des champs distincts pour les propriétés d'un double) et inversement, puis compare si le résultat est différent. Si elle est différente, la valeur exacte est utilisée (comme dans votre valeur intermédiaire avec 6.9 - i ) si elle est identique, la "jolie valeur" est utilisé.

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

Cette barrière à 15 chiffres est codée en dur et ne peut être modifiée qu'en recompilant la CLI, en utilisant Mono ou en appelant Microsoft et en les convaincant d'ajouter une option permettant d'imprimer une "précision" complète. (ce n'est pas vraiment de la précision, mais par le manque d'un meilleur mot). Il est probablement plus facile de calculer vous-même la précision de 52 bits ou d'utiliser la bibliothèque mentionnée précédemment.

EDIT: si vous souhaitez expérimenter vous-même les points flottants IEE-754, considérez cet outil en ligne , qui vous montre toutes les parties pertinentes d’une virgule flottante.

Utiliser

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

Cela vous donnera tous les 17 chiffres qu’il contient. Par défaut, une valeur Double contient une précision de 15 décimales, mais un maximum de 17 chiffres est conservé en interne. {0: R} ne vous donnera pas toujours 17 chiffres, il en donnera 15 si le nombre peut être représenté avec cette précision.

qui retourne 15 chiffres si le nombre peut être représenté avec cette précision ou 17 chiffres si le nombre ne peut être représenté qu'avec une précision maximale. Il n’ya rien que vous puissiez faire pour faire en sorte que le double retour ait plus de chiffres, c’est ainsi que cela est mis en œuvre. Si vous ne l'aimez pas, faites vous-même une nouvelle double classe ...

Le double de .NET ne peut stocker plus de chiffres que 17, vous ne pouvez donc pas voir 6.8999999999999999946709 dans le débogueur que vous verriez 6.8999999999999995. Veuillez fournir une image pour prouver que nous avons tort.

La réponse à cette question est simple et peut être trouvée sur MSDN

  

N'oubliez pas qu'un nombre à virgule flottante ne peut se rapprocher qu'un nombre décimal, et que la précision d'un nombre à virgule flottante détermine la précision avec laquelle ce nombre se rapproche d'un nombre décimal. Par défaut, une valeur Double contient 15 décimales de précision , mais 17 caractères au maximum sont conservés en interne.

Dans votre exemple, la valeur de i est 6.8999999999999999946709, qui porte le numéro 9 pour toutes les positions comprises entre le 3ème et le 16ème chiffre (n'oubliez pas de compter la partie entière dans les chiffres). Lors de la conversion en chaîne, le cadre arrondit le nombre au 15ème chiffre.

i     = 6.89999999999999 946709
digit =           111111 111122
        1 23456789012345 678901

J'ai essayé de reproduire vos conclusions, mais lorsque j'ai regardé "i" dans le débogueur, il s'est avéré que "6.899999999999999995" n'était pas "6.8999999999999999946709" comme vous l'avez écrit dans la question. Pouvez-vous indiquer comment reproduire ce que vous avez vu?

Pour voir ce que le débogueur vous montre, vous pouvez utiliser un DoubleConverter comme dans la ligne de code suivante:

Console.WriteLine(TypeDescriptor.GetConverter(i).ConvertTo(i, typeof(string)));

J'espère que ça aide!

Éditer: Je suppose que je suis plus fatigué que je ne le pensais, bien sûr, cela revient à formater à la valeur aller-retour (comme mentionné précédemment).

La réponse est oui, la double impression est interrompue dans .NET, ils sont en train d'imprimer les derniers chiffres.

Vous pouvez lire comment le mettre en œuvre correctement ici .

J'ai eu à faire de même pour IronScheme.

> (* 10.0 0.69)
6.8999999999999995
> 6.89999999999999946709
6.8999999999999995
> (- 6.9 (* 10.0 0.69))
8.881784197001252e-16
> 6.9
6.9
> (- 6.9 8.881784197001252e-16)
6.8999999999999995

Remarque: C et C # ont une valeur correcte, mais l'impression est interrompue.

Mise à jour: je suis toujours à la recherche de la conversation que j'ai eue sur la liste de diffusion et qui a conduit à cette découverte.

J'ai trouvé cette solution rapide.

    double i = 10 * 0.69;
    System.Diagnostics.Debug.WriteLine(i);


    String s = String.Format("{0:F20}", i).Substring(0,20);
    System.Diagnostics.Debug.WriteLine(s + " " +s.Length );

Console.WriteLine (string.Format ("Les frais de cours sont égaux à {0: 0.00}", + cfees));

internal void DisplaycourseDetails()
        {
            Console.WriteLine("Course Code : " + cid);
            Console.WriteLine("Couse Name : " + cname);
            //Console.WriteLine("Couse Name : " + string.Format("{0:0.00}"+ cfees));
            // string s = string.Format("Course Fees is {0:0.00}", +cfees);
            // Console.WriteLine(s);
            Console.WriteLine( string.Format("Course Fees is {0:0.00}", +cfees));
        }
        static void Main(string[] args)
        {
            Course obj1 = new Course(101, "C# .net", 1100.00);
            obj1.DisplaycourseDetails();
            Course obj2 = new Course(102, "Angular", 7000.00);
            obj2.DisplaycourseDetails();
            Course obj3 = new Course(103, "MVC", 1100.00);
            obj3.DisplaycourseDetails();
            Console.ReadLine();
        }
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top