Lancer un résultat pour flotter dans la méthode retourne des modifications de float

Pourquoi ce code imprime-t-il False dans .NET 4? Il semble qu’un comportement inattendu soit causé par la dissortingbution explicite.

J’aimerais qu’une réponse au-delà de “virgule flottante soit inexacte” ou “ne fais pas cela”.

 float a(float x, float y) { return ( x * y ); } float b(float x, float y) { return (float)( x * y ); } void Main() { Console.WriteLine( a( 10f, 1f/10f ) == b( 10f, 1f/10f ) ); } 

PS: Ce code provient d’un test unitaire, pas du code de publication. Le code a été écrit de cette manière délibérément. Je me doutais que cela échouerait éventuellement, mais je voulais savoir exactement quand et exactement pourquoi. La réponse prouve la validité de cette technique car elle fournit une compréhension qui dépasse la compréhension habituelle du déterminisme à virgule flottante. Et c’était le point d’écrire ce code de cette façon; exploration délibérée.

PPS: Le test unitaire passait en .NET 3.5, mais échoue maintenant après la mise à niveau vers .NET 4.

Le commentaire de David est correct mais insuffisamment fort. Il n’ya aucune garantie que ce calcul effectué deux fois dans le même programme produira les mêmes résultats.

La spécification C # est extrêmement claire sur ce point:


Les opérations à virgule flottante peuvent être effectuées avec une précision supérieure à celle du type de résultat de l’opération. Par exemple, certaines architectures matérielles prennent en charge un type à virgule flottante «étendu» ou «à double long» avec une scope et une précision supérieures à celles du type double et exécutent implicitement toutes les opérations à virgule flottante utilisant ce type de précision supérieure. Ce n’est qu’à un coût de performances excessif que de telles architectures matérielles peuvent effectuer des opérations en virgule flottante avec moins de précision et, plutôt que d’imposer une implémentation aux performances et à la précision, C # permet d’utiliser un type plus précis pour toutes les opérations en virgule flottante . Outre des résultats plus précis, cela a rarement des effets mesurables. Cependant, dans les expressions de la forme x * y / z , où la multiplication produit un résultat en dehors de la plage double, mais la division suivante ramène le résultat temporaire dans la plage double, le fait que l’expression est évaluée dans une valeur supérieure. Le format de plage peut provoquer un résultat fini au lieu d’une infinité.


Le compilateur C #, le jitter et le runtime ont tous une large scope pour vous donner des résultats plus précis que ceux requirejs par la spécification, à tout moment, à leur gré. Ils ne sont pas obligés de choisir de le faire de manière cohérente. ne pas.

Si vous n’aimez pas cela, n’utilisez pas de nombres à virgule flottante binarys. soit utiliser des décimales ou des rationnels de précision arbitraires.

Je ne comprends pas pourquoi lancer pour flotter dans une méthode qui retourne float fait la différence

Excellent point

Votre exemple de programme montre comment de petites modifications peuvent entraîner des effets importants. Vous notez que dans certaines versions du runtime, la conversion en float donne explicitement un résultat différent de ne pas le faire. Lorsque vous lancez explicitement un flottant, le compilateur C # donne un indice à l’environnement d’ exécution pour dire “retirez cette chose du mode de très haute précision si vous utilisez cette optimisation”. Comme l’indique la spécification, cela a un coût de performance potentiel.

Que cela arrive pour arriver à la “bonne réponse” est simplement un heureux accident; la bonne réponse est obtenue car, dans ce cas, la perte de précision est arrivée à la perdre dans la bonne direction .

En quoi le .net 4 est-il différent?

Vous demandez quelle est la différence entre 3,5 et 4,0 temps d’exécution; La différence est clairement que dans la version 4.0, le gigue choisit une précision plus élevée dans votre cas particulier, et la gigue 3.5 choisit de ne pas le faire. Cela ne signifie pas que cette situation était impossible en 3.5; cela a été possible dans toutes les versions du runtime et dans toutes les versions du compilateur C #. Vous êtes juste tombé sur un cas où, sur votre machine, ils diffèrent dans leurs détails. Mais la gigue a toujours été autorisée à faire cette optimisation, et l’a toujours fait à sa guise.

Le compilateur C # a également tout à fait le droit de choisir de faire des optimisations similaires lors du calcul des flottants constants au moment de la compilation. Deux calculs apparemment identiques dans les constantes peuvent avoir des résultats différents selon les détails de l’état d’exécution du compilateur.

Plus généralement, votre attente selon laquelle les nombres à virgule flottante devraient avoir les propriétés algébriques des nombres réels est totalement décalée par rapport à la réalité. ils n’ont pas ces propriétés algébriques. Les opérations en virgule flottante ne sont même pas associatives ; ils n’obéissent certainement pas aux lois des inverses multiplicatifs, comme vous semblez vous y attendre. Les nombres à virgule flottante ne sont qu’une approximation de l’arithmétique réelle. une approximation assez proche pour, par exemple, simuler un système physique, ou calculer des statistiques récapitulatives, etc.

Je n’ai pas de compilateur Microsoft en ce moment et Mono n’a pas cet effet. Autant que je sache, GCC 4.3+ utilise gmp et mpfr pour calculer certains éléments lors de la compilation . Le compilateur C # peut faire la même chose pour les méthodes non virtuelles, statiques ou privées dans un même assemblage. Un casting explicite peut interférer avec une telle optimisation (mais je ne vois aucune raison pour laquelle il ne peut pas avoir le même comportement). C’est-à-dire que cela peut aller de pair avec le calcul d’une expression constante à un certain niveau (car b() peut être par exemple jusqu’à la dissortingbution).

GCC a également l’optimisation qui favorise le fonctionnement avec une plus grande précision si cela est logique.

Donc, je considère les deux optimisations comme une raison potentielle. Mais pour les deux d’entre eux, je ne vois aucune raison pour laquelle la diffusion explicite du résultat pourrait avoir un sens supplémentaire, comme “être plus proche de la norme”.