42 votes

C # Pourquoi des nombres décimaux identiques peuvent-ils produire des valeurs de hachage inégales?

Nous avons couru dans la magie nombre décimal qui a brisé notre table de hachage. J'ai bouilli vers le bas à la suite minimal de cas:

decimal d0 = 295.50000000000000000000000000m;
decimal d1 = 295.5m;

Console.WriteLine("{0} == {1} : {2}", d0, d1, (d0 == d1));
Console.WriteLine("0x{0:X8} == 0x{1:X8} : {2}", d0.GetHashCode(), d1.GetHashCode()
                  , (d0.GetHashCode() == d1.GetHashCode()));

Donner le résultat suivant:

295.50000000000000000000000000 == 295.5 : True
0xBF8D880F == 0x40727800 : False

Ce qui est vraiment particulière: modifier, d'ajouter ou de supprimer tous les chiffres dans d0 et le problème disparaît. Même en ajoutant ou en supprimant un des zéros de fin! Le signe ne semble pas à la matière même si.

Notre solution est de diviser la valeur de se débarrasser de la des zéros à la fin, comme ceci:

decimal d0 = 295.50000000000000000000000000m / 1.000000000000000000000000000000000m;

Mais ma question est, comment est C# faire ce mal?

28voto

Jon Skeet Points 692016

Pour commencer, C# n'est pas de faire quelque chose de mal à tous. C'est un cadre de bug.

Il semble en effet comme un bug bien - fondamentalement, quelle que soit la normalisation est impliqué dans la comparaison pour l'égalité devrait être utilisé de la même manière pour le code de hachage de calcul. J'ai vérifié et peuvent se reproduire trop (à l'aide .NET 4) y compris la vérification de l' Equals(decimal) et Equals(object) méthodes ainsi que l' == de l'opérateur.

Il a certainement dirait que c'est la d0 de la valeur qui est le problème, comme l'ajout de fuite de 0 à d1 ne change pas les résultats (jusqu'à ce que c'est le même que d0 bien sûr). Je soupçonne, il y a quelques cas de coin déclenchée par l'exacte représentation binaire de là.

Je suis surpris qu'il ne l'est pas (et comme vous le dites, il travaille la plupart du temps), mais vous devez signaler le bug sur se Connecter.

4voto

CoperNick Points 484

Un autre bug (?) que les résultats dans les différents octets de la représentation pour la même décimales sur les différents compilateurs: compiler le code suivant sur VS 2005, puis par rapport à 2010. Ou regardez mon article sur le Projet de Code.

class Program
{
    static void Main(string[] args)
    {
        decimal one = 1m;

        PrintBytes(one);
        PrintBytes(one + 0.0m); // compare this on different compilers!
        PrintBytes(1m + 0.0m);

        Console.ReadKey();
    }

    public static void PrintBytes(decimal d)
    {
        MemoryStream memoryStream = new MemoryStream();
        BinaryWriter binaryWriter = new BinaryWriter(memoryStream);

        binaryWriter.Write(d);

        byte[] decimalBytes = memoryStream.ToArray();

        Console.WriteLine(BitConverter.ToString(decimalBytes) + " (" + d + ")");
    }
}

Certaines personnes utilisent suivants code de la normalisation d=d+0.0000m qui ne fonctionne pas correctement sur VS 2010. Votre code de la normalisation (d=d/1.000000000000000000000000000000000m) semble bon - je utiliser le même pour obtenir le même tableau d'octets pour le même nombre de décimales.

3voto

AxelEckenberger Points 9546

Couru dans ce bug aussi ... :-(

Les tests (voir ci-dessous) indiquent que cela dépend de la précision maximale disponible pour la valeur. Les codes de hachage erronés ne se produisent que près de la précision maximale pour la valeur donnée. Comme les tests le montrent, l’erreur semble dépendre des chiffres à gauche du point décimal. Parfois, le seul code de hachage pour maxDecimalDigits - 1 est incorrect, parfois la valeur de maxDecimalDigits est incorrecte.

 var data = new decimal[] {
//    123456789012345678901234567890
    1.0m,
    1.00m,
    1.000m,
    1.0000m,
    1.00000m,
    1.000000m,
    1.0000000m,
    1.00000000m,
    1.000000000m,
    1.0000000000m,
    1.00000000000m,
    1.000000000000m,
    1.0000000000000m,
    1.00000000000000m,
    1.000000000000000m,
    1.0000000000000000m,
    1.00000000000000000m,
    1.000000000000000000m,
    1.0000000000000000000m,
    1.00000000000000000000m,
    1.000000000000000000000m,
    1.0000000000000000000000m,
    1.00000000000000000000000m,
    1.000000000000000000000000m,
    1.0000000000000000000000000m,
    1.00000000000000000000000000m,
    1.000000000000000000000000000m,
    1.0000000000000000000000000000m,
    1.00000000000000000000000000000m,
    1.000000000000000000000000000000m,
    1.0000000000000000000000000000000m,
    1.00000000000000000000000000000000m,
    1.000000000000000000000000000000000m,
    1.0000000000000000000000000000000000m,
};

for (int i = 0; i < 1000; ++i)
{
    var d0 = i * data[0];
    var d0Hash = d0.GetHashCode();
    foreach (var d in data)
    {
        var value = i * d;
        var hash = value.GetHashCode();
        Console.WriteLine("{0};{1};{2};{3};{4};{5}", d0, value, (d0 == value), d0Hash, hash, d0Hash == hash);
    }
}
 

1voto

Cyan Points 3186

C'est un nombre décimal de l'erreur d'arrondi.

Trop de précision est nécessaire pour définir d0 avec le .000000000000000, comme une conséquence de l'algorithme en charge de la il fait une erreur et finit par donner un résultat différent. Il pourrait être classé comme un bug dans cet exemple, bien que la note que "décimal" le type est censé avoir une précision de 28 chiffres, et ici, vous êtes exigeant une précision de 29 chiffres de d0.

Cela peut être testé en demandant la pleine raw représentation hexadécimale de d0 et d1.

1voto

J... Points 8994

J'ai testé cette dans VB.NET (v3.5) et a obtenu la même chose.

La chose intéressante à propos des codes de hachage :

A) 0x40727800 = 1081243648

B) 0xBF8D880F = -1081243648

À L'Aide De Chiffres Après La Virgule.GetBits() j'ai trouvé

format : Mantisse (hhhhhhhh hhhhhhhh hhhhhhhh) Exposant(seee0000) (h est des valeurs, 's' est un signe, un " e " exposant, 0 doit être zéros)

d1 ==> 00000000 00000000 00000B8B - 00010000 = (2955 / 10 ^ 1) = 295.5

faire ==> 5F7B2FE5 D8EACD6E 2E000000 - 001A0000

...qui convertit 29550000000000000000000000000 / 10^26 = 295.5000000...etc

** edit : ok, j'ai écrit un cryptage de 128 bits hexadécimal-décimal de la calculatrice et le ci-dessus est tout à fait correct

Cela ressemble fort à une conversion interne bug de la sorte. Microsoft déclare explicitement qu'ils ne garantissent pas leur implémentation par défaut de GetHashCode. Si vous l'utilisez pour quelque chose d'important alors il est probablement logique d'écrire votre propre GetHashCode pour le type décimal. La mise en forme d'un décimal fixe, fixe la largeur de la chaîne et de hachage semble fonctionner, par exemple (>29 décimales, > 58 largeur s'adapte à toutes les éventuelles décimales).

* edit : je ne sais pas à propos de ça. Elle doit être une erreur de conversion quelque part depuis la stockées précision change fondamentalement la valeur réelle de la mémoire. Que les codes de hachage fin comme signé négatifs des uns et des autres est un gros indice - aurait besoin de chercher plus loin par défaut dans le code de hachage de mise en œuvre pour en savoir plus.

28 ou 29 chiffres ne compte pas moins qu'il est dépendant de code qui ne permet pas d'évaluer les limites extérieures correctement. Le plus grand de 96 bits entier est accessible :

79228162514264337593543950335

vous pouvez donc avoir 29 chiffres, aussi longtemps que la chose en entier (sans virgule) est inférieur à cette valeur. Je ne peux pas aider mais penser que c'est quelque chose de beaucoup plus subtil dans le code de hachage de calcul quelque part.

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