J'ai testé ce code sur https://dotnetfiddle.net/ :
using System;
public class Program
{
const float scale = 64 * 1024;
public static void Main()
{
Console.WriteLine(unchecked((uint)(ulong)(1.2 * scale * scale + 1.5 * scale)));
Console.WriteLine(unchecked((uint)(ulong)(scale* scale + 7)));
}
}
Si je compile avec .NET 4.7.2, j'obtiens
859091763
7
Mais si je fais Roslyn ou .NET Core, je reçois
859091763
0
Pourquoi cela arrive-t-il?
ulong
est ignorée dans ce dernier cas, donc cela se produit dans la conversionfloat
->int
.ulong
affecte le résultat.Réponses:
Mes conclusions étaient incorrectes. Voir la mise à jour pour plus de détails.
Ressemble à un bogue dans le premier compilateur que vous avez utilisé. Zéro est le résultat correct dans ce cas .L'ordre des opérations dicté par la spécification C # est le suivant:scale
parscale
, donnanta
a + 7
, céderb
b
surulong
, donnantc
c
suruint
, donnantd
Les deux premières opérations vous laissent avec une valeur flottante de
b = 4.2949673E+09f
. Sous l'arithmétique à virgule flottante standard, c'est4294967296
( vous pouvez le vérifier ici ). Cela s'intègreulong
très bien, doncc = 4294967296
, mais c'est exactement un de plusuint.MaxValue
, donc il y a des allers-retours0
, par conséquentd = 0
. Maintenant, surprise surprise, puisque l' arithmétique à virgule flottante est génial,4.2949673E+09f
et4.2949673E+09f + 7
est exactement le même nombre dans la norme IEEE 754. Alorsscale * scale
vous donnera la même valeur d'unfloat
quescale * scale + 7
,a = b
, de sorte que les deuxièmes opérations est essentiellement un no-op.Le compilateur Roslyn effectue (certaines) opérations const au moment de la compilation et optimise cette expression entière pour
0
.Encore une fois, c'est le résultat correct , etle compilateur est autorisé à effectuer toutes les optimisations qui entraîneront le même comportement exact que le code sans elles.Je suppose que le compilateur .NET 4.7.2 que vous avez utilisé essaie également d'optimiser cela, mais a un bogue qui lui fait évaluer la distribution au mauvais endroit. Naturellement, si vous effectuez d'abord un castscale
vers unuint
, puis effectuez l'opération, vous obtenez7
, car desscale * scale
allers-retours vers0
et ensuite vous ajoutez7
. Mais cela n'est pas cohérent avec le résultat que vous obtiendriez lors de l'évaluation des expressions étape par étape lors de l'exécution . Encore une fois, la cause première est juste une supposition en regardant le comportement produit, mais étant donné tout ce que j'ai dit ci-dessus, je suis convaincu que c'est une violation des spécifications du côté du premier compilateur.MISE À JOUR:
J'ai fait une gaffe. Il y a ce morceau de la spécification C # que je ne connaissais pas lors de l'écriture de la réponse ci-dessus:
C # garantit des opérations pour fournir un niveau de précision au moins au niveau de l'IEEE 754, mais pas nécessairement exactement cela. Ce n'est pas un bug, c'est une fonctionnalité spécifique. Le compilateur Roslyn est en droit d'évaluer l'expression exactement comme le spécifie IEEE 754, et l'autre compilateur est en droit de déduire que
2^32 + 7
c'est7
lorsqu'il est placé dansuint
.Je suis désolé pour ma première réponse trompeuse, mais au moins nous avons tous appris quelque chose aujourd'hui.
la source
scale
par la valeur flottante et évaluait ensuite tout le reste à l'exécution, le résultat serait le même. Peux-tu élaborer?Le point ici est (comme vous pouvez le voir sur les documents ) que les valeurs flottantes ne peuvent avoir qu'une base jusqu'à 2 ^ 24 . Ainsi, lorsque vous affectez une valeur de 2 ^ 32 ( 64 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32 ), cela devient en fait 2 ^ 24 * 2 ^ 8 , qui est 4294967000 . Ajouter 7 ne fera qu'ajouter à la partie tronquée par conversion en ulong .
Si vous changez en double , qui a une base de 2 ^ 53 , cela fonctionnera pour ce que vous voulez.
Cela peut être un problème au moment de l'exécution mais, dans ce cas, c'est un problème au moment de la compilation, car toutes les valeurs sont des constantes et seront évaluées par le compilateur.
la source
Tout d'abord, vous utilisez un contexte non contrôlé qui est une instruction pour le compilateur, vous êtes sûr, en tant que développeur, que le résultat ne débordera pas de type et vous ne souhaitez voir aucune erreur de compilation. Dans votre scénario, vous êtes en fait de type débordant et attendez-vous à un comportement cohérent entre trois compilateurs différents, dont l'un est probablement rétrocompatible loin de l'historique par rapport à Roslyn et .NET Core qui sont nouveaux.
La deuxième chose est que vous mélangez des conversions implicites et explicites. Je ne suis pas sûr du compilateur Roslyn, mais les compilateurs .NET Framework et .NET Core peuvent certainement utiliser des optimisations différentes pour ces opérations.
Le problème ici est que la première ligne de votre code utilise uniquement des valeurs / types à virgule flottante, mais la deuxième ligne est une combinaison de valeurs / types à virgule flottante et de valeur / type intégrale.
Si vous créez immédiatement un type à virgule flottante entier (7> 7.0), vous obtiendrez le même résultat pour les trois sources compilées.
Donc, je dirais à l'opposé de ce que V0ldek a répondu et c'est "Le bogue (s'il s'agit vraiment d'un bogue) est très probablement dans les compilateurs Roslyn et .NET Core".
Une autre raison de croire que le résultat du premier calcul non contrôlé est le même pour tous et que la valeur dépasse la valeur maximale de
UInt32
type.Moins un est là car nous partons de zéro, ce qui est une valeur difficile à soustraire. Si ma compréhension mathématique du débordement est correcte, nous commençons par le nombre suivant après la valeur maximale.
MISE À JOUR
Selon le commentaire de Jalsh
Son commentaire est correct. Dans le cas où nous utilisons float, vous obtenez toujours 0 pour Roslyn et .NET Core, mais en revanche, en utilisant des résultats doubles en 7.
J'ai fait des tests supplémentaires et les choses deviennent encore plus bizarres, mais au final tout a du sens (au moins un peu).
Ce que je suppose, c'est que le compilateur .NET Framework 4.7.2 (publié mi-2018) utilise vraiment des optimisations différentes de celles des compilateurs .NET Core 3.1 et Roslyn 3.4 (publiées fin 2019). Ces différentes optimisations / calculs sont purement utilisés pour des valeurs constantes connues au moment de la compilation. C’est pourquoi il fallait utiliser
unchecked
mots clés car le compilateur sait déjà qu'il y a un débordement, mais différents calculs ont été utilisés pour optimiser l'IL final.Même code source et presque même IL sauf instruction IL_000a. Un compilateur calcule 7 et les autres 0.
Code source
.NET Framework (x64) IL
Branche du compilateur Roslyn (sept. 2019) IL
Cela commence à aller dans le bon sens lorsque vous ajoutez des expressions non constantes (par défaut sont
unchecked
) comme ci-dessous.Ce qui génère "exactement" le même IL par les deux compilateurs.
.NET Framework (x64) IL
Branche du compilateur Roslyn (sept. 2019) IL
Donc, à la fin, je crois que la raison d'un comportement différent est simplement une version différente du framework et / ou du compilateur qui utilise différentes optimisations / calculs pour des expressions constantes, mais dans d'autres cas, le comportement est très identique.
la source