He estado probando este código en 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 compilo con .NET 4.7.2 obtengo
859091763
7 7
Pero si hago Roslyn o .NET Core, obtengo
859091763
0 0
¿Por qué pasó esto?
ulong
se ignora en el último caso, por lo que sucede en la conversiónfloat
->int
.ulong
afecta el resultado.Respuestas:
Mis conclusiones fueron incorrectas. Vea la actualización para más detalles.
Parece un error en el primer compilador que usaste. Cero es el resultado correcto en este caso .El orden de operaciones dictado por la especificación de C # es el siguiente:scale
porscale
, produciendoa
a + 7
, cediendob
b
aulong
, cediendoc
c
auint
, cediendod
Las dos primeras operaciones te dejan con un valor flotante de
b = 4.2949673E+09f
. Bajo la aritmética de coma flotante estándar, esto es4294967296
( puede verificarlo aquí ). Eso encajaulong
bien, así quec = 4294967296
, pero es exactamente uno más queuint.MaxValue
, por lo que es de ida y vuelta a0
tantod = 0
. Ahora, sorpresa sorpresa, ya aritmética de punto flotante es muy de moda,4.2949673E+09f
y4.2949673E+09f + 7
es exactamente el mismo número en el estándar IEEE 754. Así quescale * scale
le dan el mismo valor de unafloat
comoscale * scale + 7
,a = b
, por lo que las segundas operaciones es básicamente un no-op.El compilador de Roslyn realiza (algunas) operaciones constantes en tiempo de compilación y optimiza toda esta expresión para
0
.Nuevamente, ese es el resultado correcto , yel compilador puede realizar cualquier optimización que dé como resultado el mismo comportamiento que el código sin ellos.Mi conjetura es que el compilador .NET 4.7.2 ha utilizado también trata de optimizar esta distancia, pero tiene un error que hace que se evalúe el molde en un lugar equivocado. Naturalmente, si primero lanzasscale
a unauint
y luego realizas la operación, obtienes7
, porquescale * scale
los viajes de ida y vuelta0
añaden7
. Pero eso es inconsistente con el resultado que obtendría al evaluar las expresiones paso a paso en tiempo de ejecución . Nuevamente, la causa raíz es solo una suposición cuando se observa el comportamiento producido, pero dado todo lo que he dicho anteriormente, estoy convencido de que se trata de una violación de las especificaciones del lado del primer compilador.ACTUALIZAR:
He hecho una tontería. Hay poco de la especificación de C # que no sabía que existía al escribir la respuesta anterior:
C # garantiza operaciones para proporcionar un nivel de precisión al menos en el nivel de IEEE 754, pero no necesariamente eso exactamente . No es un error, es una característica específica. El compilador Roslyn está en su derecho de evaluar la expresión exactamente como IEEE 754 especifica, y el otro compilador está en su derecho a deducir que
2^32 + 7
es7
cuando se pone enuint
.Lamento mi primera respuesta engañosa, pero al menos todos hemos aprendido algo hoy.
fuente
scale
con el valor flotante y luego evaluara todo lo demás en tiempo de ejecución, el resultado sería el mismo. ¿Puedes elaborar?El punto aquí es (como puede ver en los documentos ) que los valores flotantes solo pueden tener una base de hasta 2 ^ 24 . Entonces, cuando asigna un valor de 2 ^ 32 ( 64 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32 ) se convierte, en realidad, en 2 ^ 24 * 2 ^ 8 , que es 4294967000 . Agregar 7 solo se agregará a la parte truncada por la conversión a ulong .
Si cambia a doble , que tiene una base de 2 ^ 53 , funcionará para lo que desee.
Esto podría ser un problema de tiempo de ejecución pero, en este caso, es un problema de tiempo de compilación, porque todos los valores son constantes y serán evaluados por el compilador.
fuente
En primer lugar, está utilizando un contexto no comprobado, que es una instrucción para el compilador, está seguro, como desarrollador, de que el resultado no se desbordará y no le gustaría ver ningún error de compilación. En su escenario, en realidad está desbordando el tipo y esperando un comportamiento consistente en tres compiladores diferentes, uno de los cuales probablemente sea compatible con versiones anteriores en comparación con Roslyn y .NET Core, que son nuevos.
Lo segundo es que estás mezclando conversiones implícitas y explícitas. No estoy seguro sobre el compilador de Roslyn, pero definitivamente .NET Framework y .NET Core pueden usar diferentes optimizaciones para esas operaciones.
El problema aquí es que la primera línea de su código usa solo valores / tipos de punto flotante, pero la segunda línea es una combinación de valores / tipos de punto flotante y tipo / valor integral.
En caso de que haga un tipo entero de coma flotante de inmediato (7> 7.0) obtendrá el mismo resultado para las tres fuentes compiladas.
Entonces, diría lo contrario de lo que respondió V0ldek y que es "El error (si realmente es un error) es muy probable en los compiladores Roslyn y .NET Core".
Otra razón para creer eso es que el resultado de los primeros resultados de cálculo no verificados son los mismos para todos y es un valor que desborda el valor máximo de
UInt32
tipo.Menos uno está allí cuando comenzamos desde cero, que es un valor que es difícil de restar. Si mi comprensión matemática del desbordamiento es correcta, comenzamos desde el siguiente número después del valor máximo.
ACTUALIZAR
Según el comentario de jalsh
Su comentario es correcto. En caso de que usemos flotante, todavía obtendrá 0 para Roslyn y .NET Core, pero por otro lado, usará resultados dobles en 7.
Hice algunas pruebas adicionales y las cosas se ponen aún más extrañas, pero al final todo tiene sentido (al menos un poco).
Lo que supongo es que el compilador .NET Framework 4.7.2 (lanzado a mediados de 2018) realmente usa diferentes optimizaciones que los compiladores .NET Core 3.1 y Roslyn 3.4 (lanzado a fines de 2019). Estas diferentes optimizaciones / cálculos se utilizan exclusivamente para valores constantes conocidos en tiempo de compilación. Por eso era necesario usar
unchecked
palabra clave ya que el compilador ya sabe que está ocurriendo un desbordamiento, pero se utilizó un cálculo diferente para optimizar la IL final.El mismo código fuente y casi la misma IL, excepto la instrucción IL_000a. Un compilador calcula 7 y otro 0.
Código fuente
.NET Framework (x64) IL
Roslyn compilador branch (Sep 2019) IL
Comienza a ir por el camino correcto cuando agrega expresiones no constantes (por defecto son
unchecked
) como a continuación.Lo que genera "exactamente" la misma IL por ambos compiladores.
.NET Framework (x64) IL
Roslyn compilador branch (Sep 2019) IL
Entonces, al final, creo que la razón de un comportamiento diferente es solo una versión diferente del framework y / o compilador que está usando diferentes optimizaciones / cálculos para expresiones constantes, pero en otros casos el comportamiento es muy similar.
fuente