Перш за все, ви використовуєте неперевірений контекст, який є інструкцією для компілятора, ви впевнені, як розробник, що результат не буде переповнювати тип, і ви хочете бачити помилку компіляції. У вашому сценарії ви насправді навмисно переповнюєте тип і очікуєте послідовної поведінки у трьох різних компіляторах, один з яких, ймовірно, є відсталим до історії, порівняно з Roslyn та .NET Core, які є новими.
Друге, що ви змішуєте неявні та явні перетворення. Я не впевнений у компіляторі Roslyn, але, безумовно, компілятори .NET Framework та .NET Core можуть використовувати різні оптимізації для цих операцій.
Проблема тут полягає в тому, що перший рядок коду використовує лише значення / типи з плаваючою комою, а другий рядок - це комбінація значень / типів з плаваючою комою та цілісного значення / типу.
Якщо ви відразу зробите цілий число з плаваючою точкою (7> 7.0), ви отримаєте однаковий результат для всіх трьох складених джерел.
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
}
}
Отже, я б сказав протилежне тому, що відповів V0ldek, і це "Помилка (якщо вона справді є помилкою), швидше за все, у компіляторах Roslyn і .NET Core".
Ще одна причина вважати, що результат перших неперевірених обчислень однаковий для всіх, і саме значення переповнює максимальне значення UInt32
типу.
Console.WriteLine(unchecked((uint)(ulong)(1.2 * scale * scale + 1.5 * scale) - UInt32.MaxValue - 1)); // 859091763
Мінус один є, коли ми починаємо з нуля, це значення, яке важко відняти. Якщо моє математичне розуміння переповнення правильне, ми починаємо з наступного числа після максимального значення.
ОНОВЛЕННЯ
За коментарем jalash
7.0 - це подвійний, а не плаваючий, спробуйте 7.0f, він все одно дасть вам 0
Його коментар правильний. Якщо ми використовуємо float, ви все одно отримаєте 0 для Roslyn та .NET Core, але з іншого боку, використовуючи подвійні результати у 7.
Я зробив кілька додаткових тестів, і речі стають ще більш дивними, але зрештою все має сенс (принаймні трохи).
Я припускаю, що компілятор .NET Framework 4.7.2 (випущений в середині 2018 року) дійсно використовує різні оптимізації, ніж компілятори .NET Core 3.1 та Roslyn 3.4 (вийшов наприкінці 2019 року). Ці різні оптимізації / обчислення чисто використовуються для постійних значень, відомих під час компіляції. Саме тому виникла потреба у використанніunchecked
ключового слова, оскільки компілятор вже знає, що відбувається переповнення, але для оптимізації остаточного ІР використовувались різні обчислення.
Той самий вихідний код і майже та сама IL, за винятком інструкції IL_000a. Один компілятор обчислює 7, а інший 0.
Вихідний код
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 (вересень 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
Він починає правильним шляхом, коли ви додаєте непостійні вирази (за замовчуванням є unchecked
), як нижче.
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
}
}
Що генерує "точно" той самий ІЛ обох компіляторів.
.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 (вересень 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
Отже, врешті-решт, я вважаю, що причиною різної поведінки є лише інша версія фреймворку та / або компілятора, які використовують різні оптимізації / обчислення для постійних виразів, але в інших випадках поведінка дуже однакова.
ulong
останньому випадку акторський склад ігнорується, тому він відбувається в конверсіїfloat
->int
.