89 votes

L'opérateur modulo (%) donne un résultat différent pour les différentes versions de .NET en C#.

Je suis en train de crypter l'entrée de l'utilisateur pour générer une chaîne de caractères pour le mot de passe. Mais une ligne de code donne des résultats différents dans les différentes versions du framework. Code partiel avec la valeur de la touche pressée par l'utilisateur :

Touche enfoncée : 1. Variable ascii est de 49. Valeur de 'e' et 'n' après un certain calcul :

e = 103, 
n = 143,

Math.Pow(ascii, e) % n

Résultat du code ci-dessus :

  • En .NET 3.5 (C#)

    Math.Pow(ascii, e) % n

    donne 9.0 .

  • En .NET 4 (C#)

    Math.Pow(ascii, e) % n

    donne 77.0 .

Math.Pow() donne le bon (même) résultat dans les deux versions.

Quelle en est la cause, et existe-t-il une solution ?

160voto

Douglas Points 25145

Math.Pow fonctionne sur des nombres à virgule flottante en double précision ; il ne faut donc pas s'attendre à plus que la valeur de l'indice 15-17 premiers chiffres du résultat pour être précis :

Tous les nombres à virgule flottante ont également un nombre limité de chiffres significatifs, ce qui détermine également la précision avec laquelle une valeur à virgule flottante se rapproche d'un nombre réel. A Double peut compter jusqu'à 15 chiffres décimaux de précision, bien qu'un maximum de 17 chiffres soit maintenu en interne.

Cependant, l'arithmétique modulo exige que tous les chiffres soient exacts. Dans votre cas, vous calculez 49 103 dont le résultat est composé de 175 chiffres, ce qui rend l'opération modulo sans signification dans vos deux réponses.

Pour calculer la valeur correcte, vous devez utiliser l'arithmétique de précision arbitraire, comme le propose le programme BigInteger (introduite dans .NET 4.0).

int val = (int)(BigInteger.Pow(49, 103) % 143); // gives 114

Modifier : Comme l'a fait remarquer Mark Peters dans les commentaires ci-dessous, vous devriez utiliser la fonction BigInteger.ModPow qui est destinée spécifiquement à ce type d'opération :

int val = (int)BigInteger.ModPow(49, 103, 143);   // gives 114

72voto

dasblinkenlight Points 264350

Outre le fait que votre fonction de hachage n'est pas très bonne. * Si l'on prend en compte la version de .NET, le plus gros problème de votre code n'est pas qu'il renvoie un nombre différent selon la version de .NET, mais que dans les deux cas, il renvoie un nombre totalement dénué de sens : la réponse correcte au problème est

49 103 mod 143 = est 114. ( lien vers Wolfram Alpha )

Vous pouvez utiliser ce code pour calculer cette réponse :

private static int PowMod(int a, int b, int mod) {
    if (b == 0) {
        return 1;
    }
    var tmp = PowMod(a, b/2, mod);
    tmp *= tmp;
    if (b%2 != 0) {
        tmp *= a;
    }
    return tmp%mod;
}

La raison pour laquelle votre calcul produit un résultat différent est que pour produire une réponse, vous utilisez une valeur intermédiaire qui laisse tomber la plupart des chiffres significatifs de la valeur de 49 103 nombre : seuls les 16 premiers de ses 175 chiffres sont corrects !

1230824813134842807283798520430636310264067713738977819859474030746648511411697029659004340261471771152928833391663821316264359104254030819694748088798262075483562075061997649

Les 159 chiffres restants sont tous faux. L'opération mod, cependant, cherche un résultat qui exige que chaque chiffre soit correct, y compris les tout derniers. Par conséquent, même la plus petite amélioration de la précision de Math.Pow qui a pu être mis en œuvre dans .NET 4, entraînerait une différence radicale de votre calcul, qui produit essentiellement un résultat arbitraire.

* Puisque cette question traite de l'élévation des nombres entiers à des puissances élevées dans le contexte du hachage de mots de passe, il peut s'avérer une très bonne idée de lire cette réponse lien avant de décider si votre approche actuelle doit être modifiée pour une approche potentiellement meilleure.

27voto

Habib Points 93087

Ce que vous voyez est une erreur d'arrondi en double. Math.Pow fonctionne avec le double et la différence est la suivante :

.NET 2.0 et 3.5 => var powerResult = Math.Pow(ascii, e); retours :

1.2308248131348429E+174

.NET 4.0 et 4.5 => var powerResult = Math.Pow(ascii, e); retours :

1.2308248131348427E+174

Remarquez le dernier chiffre avant E et c'est ce qui cause la différence dans le résultat. Ce n'est pas l'opérateur modulo (%) .

24voto

Joe Points 60749

La précision de la virgule flottante peut varier d'une machine à l'autre. même sur la même machine .

Cependant, les .NET font une machine virtuelle pour vos applications... mais il y a des changements de version en version.

Il ne faut donc pas compter sur lui pour obtenir des résultats constants. Pour le chiffrement, utilisez les classes fournies par le Framework plutôt que de développer vos propres classes.

10voto

Ian Ringrose Points 19115

Il y a beaucoup de réponses sur la façon dont le code est mauvais. Cependant, quant à savoir pourquoi le résultat est différent

Intel FPUs utiliser le 80 bits en interne pour obtenir plus de précision pour les résultats intermédiaires. Ainsi, si une valeur se trouve dans le registre du processeur, elle obtient 80 bits, mais lorsqu'elle est écrite sur la pile, elle est stockée à 64 bits .

Je suppose que la nouvelle version de .NET dispose d'un meilleur optimiseur dans sa compilation Just in Time (JIT), ce qui lui permet de conserver une valeur dans un registre plutôt que de l'écrire sur la pile et de la relire ensuite depuis la pile.

Il se peut que le JIT puisse maintenant retourner une valeur dans un registre plutôt que sur la pile. Ou passer la valeur à la fonction MOD dans un registre.

Voir aussi la question de Stack Overflow Quels sont les applications/avantages d'un type de données à précision étendue de 80 bits ?

D'autres processeurs, par exemple l'ARM, donneront des résultats différents pour ce code.

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