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)));
}
}
.NET 4.7.2로 컴파일하면
859091763
7
그러나 Roslyn 또는 .NET Core를 수행하면
859091763
0
왜 이런 일이 발생합니까?
답변
나의 결론은 틀렸다. 자세한 내용은 업데이트를 참조하십시오.
사용한 첫 번째 컴파일러의 버그처럼 보입니다. 이 경우 0이 올바른 결과입니다 . C # 사양에 의해 지시 된 작업 순서는 다음과 같습니다.
- 곱셈
scale
에 의해scale
산출a
- 수행
a + 7
, 항복b
- 에 캐스팅
b
하여ulong
산출c
- 에 캐스팅
c
하여uint
산출d
처음 두 연산은 부동 소수점 값을 남겨 둡니다
b = 4.2949673E+09f
. 표준 부동 소수점 산술에서 이것은
4294967296
( 여기에서 확인할 수 있습니다 ). 에 맞는지 ulong
잘 때문에 c = 4294967296
,하지만 정확히 하나의 이상이다
uint.MaxValue
에 그것 때문에 왕복, 0
따라서 d = 0
. 부동 소수점 연산은 펑키 이후 지금 깜짝 놀람, 4.2949673E+09f
및 4.2949673E+09f + 7
IEEE 754에서 동일한 번호 그래서는 scale * scale
당신에게 동일한 값을 줄 것 float
등을 scale * scale + 7
, a = b
두 번째 작업은 기본적으로 어떠한 조합 없도록.
Roslyn 컴파일러는 컴파일 타임에 (일부) const 연산을 수행하고이 전체 표현식을로 최적화합니다 0
. 다시 말하지만, 그것은 올바른 결과 이며 , 컴파일러는 최적화없이 코드와 동일한 동작을 수행하는 최적화를 수행 할 수 있습니다.
내 생각 에 사용하는 .NET 4.7.2 컴파일러도이를 최적화하려고 시도하지만 잘못된 위치에서 캐스트를 평가하는 버그가 있습니다. 먼저 캐스팅 경우 당연히 scale
에 uint
다음 작업을 수행, 당신은 얻을 수 7
있기 때문에, scale * scale
에 왕복 0
하고 다음 추가 7
. 그러나 이는 런타임에 식을 단계별로 평가할 때 얻게되는 결과와 일치하지 않습니다 . 다시 말하지만, 근본 원인은 생성 된 동작을 볼 때 추측 일 뿐이지 만 위에서 언급 한 모든 것을 감안할 때 이것이 첫 번째 컴파일러 측면의 사양 위반이라고 확신합니다.
최신 정보:
나는 바보를했다. 위의 답변을 쓸 때 알지 못하는 C # 사양 의이 비트 가 있습니다 .
부동 소수점 연산은 연산의 결과 유형보다 높은 정밀도로 수행 될 수 있습니다. 예를 들어, 일부 하드웨어 아키텍처는 더블 유형보다 범위와 정밀도가 큰 “확장”또는 “긴 더블”부동 소수점 유형을 지원하며이 고정밀 유형을 사용하여 모든 부동 소수점 연산을 암시 적으로 수행합니다. 과도한 하드웨어 비용으로 정밀도가 떨어지는 부동 소수점 연산을 수행 할 수 있으며 성능과 정밀도를 모두 상실하는 구현이 아니라 C #을 통해 모든 부동 소수점 연산에 고정밀 유형을 사용할 수 있습니다. . 보다 정확한 결과를 제공하는 것 외에는 측정 가능한 효과가 거의 없습니다. 그러나 x * y / z 형식의 표현식에서
C # 은 최소한 IEEE 754 수준 에서 정확한 수준을 제공하는 작업을 보장 하지만 반드시 정확한 것은 아닙니다 . 버그가 아니며 사양 기능입니다. Roslyn 컴파일러는 IEEE 754가 지정한대로 정확하게 식을 평가할 권리가 있으며, 다른 컴파일러는 2^32 + 7
이 7
를 넣을 때 이를 추론 할 권리가 uint
있습니다.
오해의 소지가있는 첫 번째 답변에 대해 유감스럽게 생각하지만 적어도 오늘 우리는 모두 무언가를 배웠습니다.
답변
여기에서 요점은 ( 문서에서 볼 수 있듯이 ) float 값은 최대 2 ^ 24 까지만 가질 수 있다는 것 입니다. 따라서 2 ^ 32 ( 64 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32 ) 값을 할당하면 실제로 2 ^ 24 * 2 ^ 도 8 은 4294967000 이다. 7 을 추가하면 ulong 으로 변환하여 잘린 부분에만 추가됩니다 .
밑이 2 ^ 53 인 double로 변경 하면 원하는대로 작동합니다.
이는 런타임 문제 일 수 있지만이 경우 모든 값이 상수이고 컴파일러가 평가하므로 컴파일 타임 문제입니다.
답변
우선 확인되지 않은 컨텍스트를 사용하고 있습니다.이 컴파일러는 개발자에게 결과가 오버플로 유형이 아니며 컴파일 오류가 없음을 확신합니다. 귀하의 시나리오에서는 실제로 오버 플로우 유형을 목표로하고 3 개의 다른 컴파일러에서 일관된 동작을 기대합니다.이 중 하나는 새로운 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
빼기 1은 0부터 시작하여 빼기 어려운 값입니다. 오버플로에 대한 수학 이해가 정확하면 최대 값 다음에 다음 숫자부터 시작합니다.
최신 정보
jalsh 의견에 따르면
7.0은 float가 아닌 double입니다. 7.0f를 사용해보십시오. 여전히 0을 줄 것입니다.
그의 의견은 정확하다. float를 사용하는 경우 Roslyn 및 .NET Core에 대해 여전히 0을 얻지 만 다른 한편으로는 double을 사용하면 7이됩니다.
나는 몇 가지 추가 테스트를했고 상황이 더욱 이상해졌지만 결국 모든 것이 의미가 있습니다 (적어도 조금).
내가 생각하는 것은 .NET Framework 4.7.2 컴파일러 (2018 년 중반에 릴리스 됨)는 실제로 .NET Core 3.1 및 Roslyn 3.4 컴파일러 (2019 년 말에 릴리스 됨)와 다른 최적화를 사용한다는 것입니다. 이러한 다른 최적화 / 계산은 컴파일 타임에 알려진 상수 값에 순수하게 사용됩니다. unchecked
컴파일러가 이미 오버플로가 발생한다는 것을 알고 있기 때문에 키워드 를 사용해야 할 필요가 있지만 최종 IL을 최적화하기 위해 다른 계산이 사용되었습니다.
IL_000a 명령어를 제외하고 동일한 소스 코드와 거의 동일한 IL. 한 컴파일러는 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 년 9 월) 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
}
}
두 컴파일러에서 “정확하게”동일한 IL을 생성합니다.
.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 년 9 월) 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
따라서 결국 다른 동작의 이유는 상수 식에 대해 다른 최적화 / 계산을 사용하는 다른 버전의 프레임 워크 및 / 또는 컴파일러 일뿐이지만 다른 경우 동작은 매우 동일합니다.