Lors de la comparaison des valeurs à virgule flottante pour l'égalité, il existe deux approches différentes:
NaN
n'étant pas égal à lui-même, ce qui correspond à la spécification IEEE 754 .NaN
étant égal à lui-même, ce qui fournit la propriété mathématique de la réflexivité qui est essentielle à la définition d'une relation d'équivalence
Les types à virgule flottante IEEE intégrés en C # ( float
et double
) suivent la sémantique IEEE pour ==
et !=
(et les opérateurs relationnels comme <
) mais assurent la réflexivité pour object.Equals
, IEquatable<T>.Equals
(et CompareTo
).
Considérons maintenant une bibliothèque qui fournit des structures vectorielles au-dessus de float
/ double
. Un tel type de vecteur surchargerait ==
/ !=
et remplacerait object.Equals
/ IEquatable<T>.Equals
.
Ce sur quoi tout le monde est d'accord, c'est que ==
/ !=
devrait suivre la sémantique de l'IEEE. La question est de savoir si une telle bibliothèque doit implémenter la Equals
méthode (qui est distincte des opérateurs d'égalité) d'une manière réflexive ou d'une manière qui correspond à la sémantique IEEE.
Arguments pour utiliser la sémantique IEEE pour Equals
:
- Il suit IEEE 754
C'est (peut-être beaucoup) plus rapide car il peut tirer parti des instructions SIMD
J'ai posé une question distincte sur stackoverflow sur la façon dont vous exprimeriez l'égalité réflexive à l'aide d'instructions SIMD et leur impact sur les performances: instructions SIMD pour la comparaison d'égalité en virgule flottante
Mise à jour: Il semble possible d'implémenter efficacement l'égalité réflexive à l'aide de trois instructions SIMD.
La documentation de
Equals
ne nécessite pas de réflexivité lors de l'utilisation de virgule flottante:Les instructions suivantes doivent être vraies pour toutes les implémentations de la méthode Equals (Object). Dans la liste,
x
,y
etz
représentent des références d'objets qui ne sont pas nuls.x.Equals(x)
renvoietrue
, sauf dans les cas qui impliquent des types à virgule flottante. Voir ISO / CEI / IEEE 60559: 2011, Technologies de l'information - Systèmes à microprocesseurs - Arithmétique à virgule flottante.Si vous utilisez des flottants comme clés de dictionnaire, vous vivez dans un état de péché et ne devez pas vous attendre à un comportement sain.
Arguments pour être réflexif:
Il est compatible avec les types existants, y compris
Single
,Double
,Tuple
etSystem.Numerics.Complex
.Je ne connais aucun précédent dans la BCL où
Equals
suit IEEE au lieu d'être réflexif. Les exemples comprennent contreSingle
,Double
,Tuple
etSystem.Numerics.Complex
.Equals
est principalement utilisé par les conteneurs et les algorithmes de recherche qui reposent sur la réflexivité. Pour ces algorithmes, un gain de performances n'est pas pertinent s'il les empêche de fonctionner. Ne sacrifiez pas l'exactitude à la performance.- Il casse tous les ensembles à base de hachage et des dictionnaires,
Contains
,Find
,IndexOf
sur diverses collections / LINQ, ensemble des opérations de LINQ (baseUnion
,Except
etc.) si les données contiennent desNaN
valeurs. Le code qui effectue des calculs réels où la sémantique IEEE est acceptable fonctionne généralement sur des types concrets et utilise
==
/!=
(ou des comparaisons epsilon plus probables).Actuellement, vous ne pouvez pas écrire de calculs haute performance à l'aide de génériques car vous avez besoin d'opérations arithmétiques pour cela, mais celles-ci ne sont pas disponibles via des interfaces / méthodes virtuelles.
Une
Equals
méthode plus lente n'affecterait donc pas la plupart des codes hautes performances.Il est possible de fournir une
IeeeEquals
méthode ou uneIeeeEqualityComparer<T>
pour les cas où vous avez besoin de la sémantique IEEE ou si vous avez besoin d'un avantage en termes de performances.
À mon avis, ces arguments favorisent fortement une mise en œuvre réflexive.
L'équipe CoreFX de Microsoft prévoit d'introduire un tel type de vecteur dans .NET. Contrairement à moi, ils préfèrent la solution IEEE , principalement en raison des avantages de performance. Puisqu'une telle décision ne sera certainement pas modifiée après une version finale, je veux obtenir des commentaires de la communauté sur ce que je pense être une grosse erreur.
la source
==
etEquals
retournerait des résultats différents. De nombreux programmeurs supposent qu'ils le sont et font la même chose . De plus - en général, les implémentations des opérateurs d'égalité invoquent laEquals
méthode. Vous avez fait valoir que l'on pourrait inclure unIeeeEquals
, mais on pourrait aussi le faire dans l'autre sens et inclure uneReflexiveEquals
méthode. LeVector<float>
type peut être utilisé dans de nombreuses applications critiques pour les performances et doit être optimisé en conséquence.float
/double
et plusieurs autres types,==
etEquals
sont déjà différents. Je pense qu'une incohérence avec les types existants serait encore plus déroutante que l'incohérence entre==
etEquals
vous devrez toujours faire face à d'autres types. 2) Presque tous les algorithmes / collections génériques utilisentEquals
et s'appuient sur sa réflexivité pour fonctionner (LINQ et dictionnaires), tandis que les algorithmes concrets à virgule flottante utilisent généralement==
où ils obtiennent leur sémantique IEEE.Vector<float>
une "bête" différente d'un simplefloat
oudouble
. Par cette mesure, je ne vois pas la raisonEquals
ou l'==
opérateur de se conformer à leurs normes. Vous vous êtes dit: "Si vous utilisez des flotteurs comme clés de dictionnaire, vous vivez dans un état de péché et ne vous attendez pas à un comportement sain". Si l'on devait stockerNaN
dans un dictionnaire, c'est leur propre faute pour avoir utilisé une pratique terrible. Je pense à peine que l'équipe CoreFX n'y a pas réfléchi. J'irais avec unReflexiveEquals
ou similaire, juste pour des raisons de performance.Réponses:
Je dirais que le comportement IEEE est correct.
NaN
s ne sont en aucun cas équivalents; ils correspondent à des conditions mal définies où une réponse numérique n'est pas appropriée.Au-delà des avantages de performance qui découlent de l'utilisation de l'arithmétique IEEE que la plupart des processeurs prennent en charge nativement, je pense qu'il y a un problème sémantique à dire que si
isnan(x) && isnan(y)
, alorsx == y
. Par exemple:Je dirais qu'il n'y a aucune raison valable pour laquelle on considérerait
x
égal ày
. Vous pourriez à peine conclure que ce sont des nombres équivalents; ce ne sont pas du tout des chiffres, donc cela semble être un concept totalement invalide.De plus, du point de vue de la conception d'API, si vous travaillez sur une bibliothèque à usage général qui est destinée à être utilisée par de nombreux programmeurs, il est logique d'utiliser la sémantique à virgule flottante la plus typique de l'industrie. Le but d'une bonne bibliothèque est de gagner du temps pour ceux qui l'utilisent, donc la construction d'un comportement non standard est prête à confusion.
la source
NaN == NaN
devrait retourner faux est incontesté. La question est de savoir ce que la.Equals
méthode doit faire. Par exemple, si j'utiliseNaN
comme clé de dictionnaire, la valeur associée devient non récupérable siNaN.Equals(NaN)
renvoie false.Single
, lesDouble
classes, etc. ont déjà le comportement réfléchi. À mon humble avis, c'était juste la mauvaise décision pour commencer. Mais je ne laisserais pas l'élégance entraver l'utilité / la vitesse.==
ce qui a toujours suivi IEEE, de sorte qu'ils obtiendraient le code rapide, quelle que soit la façon dont ilEquals
est implémenté. IMO, l'intérêt d'avoir uneEquals
méthode distincte consiste à utiliser des algorithmes in qui ne se soucient pas du type concret, comme laDistinct()
fonction de LINQ .==
opérateur et uneEquals()
fonction qui ont une sémantique différente. Je pense que vous payez un coût de confusion potentielle du point de vue du développeur, sans réel avantage (je n'attribue aucune valeur à la possibilité d'utiliser un vecteur de nombres comme clé de dictionnaire). C'est juste mon opinion; Je ne pense pas qu'il y ait de réponse objective à la question posée.Il y a un problème: IEEE754 définit les opérations relationnelles et l'égalité d'une manière bien adaptée aux applications numériques. Il n'est pas adapté au tri et au hachage. Donc, si vous souhaitez trier un tableau en fonction de valeurs numériques, ou si vous souhaitez ajouter des valeurs numériques à un ensemble ou les utiliser comme clés dans un dictionnaire, vous déclarez que les valeurs NaN ne sont pas autorisées, ou vous n'utilisez pas IEEE754 opérations intégrées. Votre table de hachage devrait s'assurer que tous les NaN sont mis en correspondance avec la même valeur et être comparables les uns aux autres.
Si vous définissez Vector, vous devez décider si vous souhaitez l'utiliser uniquement à des fins numériques ou s'il doit être compatible avec le tri et le hachage. Je pense personnellement que l'objectif numérique devrait être beaucoup plus important. Si le tri / hachage est nécessaire, vous pouvez écrire une classe avec Vector en tant que membre et définir le hachage et l'égalité dans cette classe comme vous le souhaitez.
la source
==
et!=
pour eux. D'après mon expérience, laEquals
méthode n'est pratiquement utilisée que par des algorithmes non numériques.