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.
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))); // 859091763
Console.WriteLine(unchecked((uint)(ulong)(scale * scale + 7.0))); // 7
}
}
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.
Console.WriteLine(unchecked((uint)(ulong)(1.2 * scale * scale + 1.5 * scale) - UInt32.MaxValue - 1)); // 859091763
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
7.0 es un doble, no un flotador, prueba 7.0f, todavía te dará un 0
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 usarunchecked
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
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)(scale * scale + 7.0)));
}
}
.NET Framework (x64) IL
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
// Fields
.field private static literal float32 scale = float32(65536)
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 17 (0x11)
.maxstack 8
IL_0000: ldc.i4 859091763
IL_0005: call void [mscorlib]System.Console::WriteLine(uint32)
IL_000a: ldc.i4.7
IL_000b: call void [mscorlib]System.Console::WriteLine(uint32)
IL_0010: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2062
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
} // end of class Program
Roslyn compilador branch (Sep 2019) IL
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Program
extends [System.Private.CoreLib]System.Object
{
// Fields
.field private static literal float32 scale = float32(65536)
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 17 (0x11)
.maxstack 8
IL_0000: ldc.i4 859091763
IL_0005: call void [System.Console]System.Console::WriteLine(uint32)
IL_000a: ldc.i4.0
IL_000b: call void [System.Console]System.Console::WriteLine(uint32)
IL_0010: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2062
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
} // end of class Program
Comienza a ir por el camino correcto cuando agrega expresiones no constantes (por defecto son unchecked
) como a continuación.
using System;
public class Program
{
static Random random = new Random();
public static void Main()
{
var scale = 64 * random.Next(1024, 1025);
uint f = (uint)(ulong)(scale * scale + 7f);
uint d = (uint)(ulong)(scale * scale + 7d);
uint i = (uint)(ulong)(scale * scale + 7);
Console.WriteLine((uint)(ulong)(1.2 * scale * scale + 1.5 * scale)); // 859091763
Console.WriteLine((uint)(ulong)(scale * scale + 7f)); // 7
Console.WriteLine(f); // 7
Console.WriteLine((uint)(ulong)(scale * scale + 7d)); // 7
Console.WriteLine(d); // 7
Console.WriteLine((uint)(ulong)(scale * scale + 7)); // 7
Console.WriteLine(i); // 7
}
}
Lo que genera "exactamente" la misma IL por ambos compiladores.
.NET Framework (x64) IL
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
// Fields
.field private static class [mscorlib]System.Random random
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 164 (0xa4)
.maxstack 4
.locals init (
[0] int32,
[1] uint32,
[2] uint32
)
IL_0000: ldc.i4.s 64
IL_0002: ldsfld class [mscorlib]System.Random Program::random
IL_0007: ldc.i4 1024
IL_000c: ldc.i4 1025
IL_0011: callvirt instance int32 [mscorlib]System.Random::Next(int32, int32)
IL_0016: mul
IL_0017: stloc.0
IL_0018: ldloc.0
IL_0019: ldloc.0
IL_001a: mul
IL_001b: conv.r4
IL_001c: ldc.r4 7
IL_0021: add
IL_0022: conv.u8
IL_0023: conv.u4
IL_0024: ldloc.0
IL_0025: ldloc.0
IL_0026: mul
IL_0027: conv.r8
IL_0028: ldc.r8 7
IL_0031: add
IL_0032: conv.u8
IL_0033: conv.u4
IL_0034: stloc.1
IL_0035: ldloc.0
IL_0036: ldloc.0
IL_0037: mul
IL_0038: ldc.i4.7
IL_0039: add
IL_003a: conv.i8
IL_003b: conv.u4
IL_003c: stloc.2
IL_003d: ldc.r8 1.2
IL_0046: ldloc.0
IL_0047: conv.r8
IL_0048: mul
IL_0049: ldloc.0
IL_004a: conv.r8
IL_004b: mul
IL_004c: ldc.r8 1.5
IL_0055: ldloc.0
IL_0056: conv.r8
IL_0057: mul
IL_0058: add
IL_0059: conv.u8
IL_005a: conv.u4
IL_005b: call void [mscorlib]System.Console::WriteLine(uint32)
IL_0060: ldloc.0
IL_0061: ldloc.0
IL_0062: mul
IL_0063: conv.r4
IL_0064: ldc.r4 7
IL_0069: add
IL_006a: conv.u8
IL_006b: conv.u4
IL_006c: call void [mscorlib]System.Console::WriteLine(uint32)
IL_0071: call void [mscorlib]System.Console::WriteLine(uint32)
IL_0076: ldloc.0
IL_0077: ldloc.0
IL_0078: mul
IL_0079: conv.r8
IL_007a: ldc.r8 7
IL_0083: add
IL_0084: conv.u8
IL_0085: conv.u4
IL_0086: call void [mscorlib]System.Console::WriteLine(uint32)
IL_008b: ldloc.1
IL_008c: call void [mscorlib]System.Console::WriteLine(uint32)
IL_0091: ldloc.0
IL_0092: ldloc.0
IL_0093: mul
IL_0094: ldc.i4.7
IL_0095: add
IL_0096: conv.i8
IL_0097: conv.u4
IL_0098: call void [mscorlib]System.Console::WriteLine(uint32)
IL_009d: ldloc.2
IL_009e: call void [mscorlib]System.Console::WriteLine(uint32)
IL_00a3: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2100
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
.method private hidebysig specialname rtspecialname static
void .cctor () cil managed
{
// Method begins at RVA 0x2108
// Code size 11 (0xb)
.maxstack 8
IL_0000: newobj instance void [mscorlib]System.Random::.ctor()
IL_0005: stsfld class [mscorlib]System.Random Program::random
IL_000a: ret
} // end of method Program::.cctor
} // end of class Program
Roslyn compilador branch (Sep 2019) IL
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Program
extends [System.Private.CoreLib]System.Object
{
// Fields
.field private static class [System.Private.CoreLib]System.Random random
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 164 (0xa4)
.maxstack 4
.locals init (
[0] int32,
[1] uint32,
[2] uint32
)
IL_0000: ldc.i4.s 64
IL_0002: ldsfld class [System.Private.CoreLib]System.Random Program::random
IL_0007: ldc.i4 1024
IL_000c: ldc.i4 1025
IL_0011: callvirt instance int32 [System.Private.CoreLib]System.Random::Next(int32, int32)
IL_0016: mul
IL_0017: stloc.0
IL_0018: ldloc.0
IL_0019: ldloc.0
IL_001a: mul
IL_001b: conv.r4
IL_001c: ldc.r4 7
IL_0021: add
IL_0022: conv.u8
IL_0023: conv.u4
IL_0024: ldloc.0
IL_0025: ldloc.0
IL_0026: mul
IL_0027: conv.r8
IL_0028: ldc.r8 7
IL_0031: add
IL_0032: conv.u8
IL_0033: conv.u4
IL_0034: stloc.1
IL_0035: ldloc.0
IL_0036: ldloc.0
IL_0037: mul
IL_0038: ldc.i4.7
IL_0039: add
IL_003a: conv.i8
IL_003b: conv.u4
IL_003c: stloc.2
IL_003d: ldc.r8 1.2
IL_0046: ldloc.0
IL_0047: conv.r8
IL_0048: mul
IL_0049: ldloc.0
IL_004a: conv.r8
IL_004b: mul
IL_004c: ldc.r8 1.5
IL_0055: ldloc.0
IL_0056: conv.r8
IL_0057: mul
IL_0058: add
IL_0059: conv.u8
IL_005a: conv.u4
IL_005b: call void [System.Console]System.Console::WriteLine(uint32)
IL_0060: ldloc.0
IL_0061: ldloc.0
IL_0062: mul
IL_0063: conv.r4
IL_0064: ldc.r4 7
IL_0069: add
IL_006a: conv.u8
IL_006b: conv.u4
IL_006c: call void [System.Console]System.Console::WriteLine(uint32)
IL_0071: call void [System.Console]System.Console::WriteLine(uint32)
IL_0076: ldloc.0
IL_0077: ldloc.0
IL_0078: mul
IL_0079: conv.r8
IL_007a: ldc.r8 7
IL_0083: add
IL_0084: conv.u8
IL_0085: conv.u4
IL_0086: call void [System.Console]System.Console::WriteLine(uint32)
IL_008b: ldloc.1
IL_008c: call void [System.Console]System.Console::WriteLine(uint32)
IL_0091: ldloc.0
IL_0092: ldloc.0
IL_0093: mul
IL_0094: ldc.i4.7
IL_0095: add
IL_0096: conv.i8
IL_0097: conv.u4
IL_0098: call void [System.Console]System.Console::WriteLine(uint32)
IL_009d: ldloc.2
IL_009e: call void [System.Console]System.Console::WriteLine(uint32)
IL_00a3: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2100
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
.method private hidebysig specialname rtspecialname static
void .cctor () cil managed
{
// Method begins at RVA 0x2108
// Code size 11 (0xb)
.maxstack 8
IL_0000: newobj instance void [System.Private.CoreLib]System.Random::.ctor()
IL_0005: stsfld class [System.Private.CoreLib]System.Random Program::random
IL_000a: ret
} // end of method Program::.cctor
} // end of class Program
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.
ulong
se ignora en el último caso, por lo que sucede en la conversiónfloat
->int
.