未检查的uint的C#溢出行为


10

我已经在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

为什么会这样?


演员要ulong在后一种情况下,所以它在发生的事情被忽略float> - int转换。
madreflection

我对行为的变化感到惊讶,这似乎是一个很大的差异。我也不希望“ 0”是那个强制转换tbh的有效答案。
卢卡斯

可以理解的 在构建Roslyn时,规范中的几件事已在编译器中修复,因此可能是其中的一部分。在SharpLab 上查看此版本的JIT输出。那显示了强制转换ulong对结果的影响。
madreflection

令人着迷的是,您的示例回到dotnetfiddle上,最后的WriteLine在Roslyn 3.4中输出0,在.NET Core 3.1中输出7
Lukas

我也在桌面上确认过。JIT代码甚至看起来都不是很紧密,.NET Core和.NET Framework确实得到了不同的结果。Trippy
Lukas

Answers:


1

我的结论不正确。请参阅更新以获取更多详细信息。

看起来像是您使用的第一个编译器中的错误。在这种情况下,零是正确的结果C#规范规定的操作顺序如下:

  1. scalescale,产生a
  2. 执行a + 7,屈服b
  3. 转换bulong,产生c
  4. 转换cuint,产生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会给你的相同值floatscale * scale + 7a = b,所以第二个操作基本上是一个无操作。

Roslyn编译器在编译时执行const操作,并将整个表达式优化为0同样,这是正确的结果,并且允许编译器执行任何优化,这些优化将导致与没有它们的代码完全相同的行为。

我的猜测是,您使用的.NET 4.7.2编译器也试图对此进行优化,但是存在一个错误,导致该错误在错误的位置评估了转换。自然地,如果您先转换scale为an uint然后执行操作,则会得到7,因为scale * scale往返于0,然后再添加7。但这与在运行时逐步评估表达式时得到的结果不一致。再次,根本原因只是在查看产生的行为时的猜测,但是鉴于我上面已经说过的一切,我相信这是第一个编译器方面的规范违规。

更新:

我做了一个蠢事。有此位的C#规范,写上面的回答时,我不知道的:

浮点运算可以比运算的结果类型更高的精度执行。例如,某些硬件体系结构支持比双精度类型具有更大范围和精度的“扩展”或“长双精度”浮点类型,并使用此更高精度类型隐式执行所有浮点运算。只有付出过高的性能代价,才能使此类硬件体系结构以较低的精度执行浮点运算,而不是要求实现同时牺牲性能和精度,而C#允许将高精度类型用于所有浮点运算。 。除了提供更精确的结果外,这几乎没有任何可测量的效果。但是,在形式为x * y / z的表达式中,

C#保证操作至少提供IEEE 754 级别的精度,但不一定完全如此。这不是错误,而是规范功能。该罗斯林编译器,它能够计算表达式严格按照IEEE 754个指定权,而其他的编译器是其推断正确的2^32 + 77投入的时候uint

对我的误导性第一答案感到抱歉,但至少我们今天已经学到了一些东西。


然后我想我们当前的.NET Framework编译器中有一个错误(我只是在VS 2019中尝试过才能确保):)我想我将尝试查看是否存在将错误记录到的地方,尽管可以解决类似问题。可能有很多有害的副作用,可能会被忽略……
卢卡斯

我不认为它会过早转换为int,这会在很多情况下引起更明显的问题,我想这里的情况是,在const操作中,它不会评估值并将其转换为最后一个值,这意味着是将中间值存储在浮点数中,而不是将中间值存储在浮点数中,而是将其替换为每个表达式本身
jalsh

@jalsh我想我不理解您的猜测。如果编译器只是简单地将每个scale值替换为float值,然后在运行时评估其他所有内容,则结果将相同。你能详细说明吗?
V0ldek

@ V0ldek,downvote是一个错误,我编辑了您的答案,所以我可以将其删除:)
jalsh

我的猜测是,它实际上并未将中间值存储在浮点数中,只是将f替换为计算f的表达式,而没有将其强制转换为浮点数
jalsh

0

关键是(如您在文档上所见)浮点值的最大底数不得超过2 ^ 24。因此,当您分配值2 ^ 3264 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32)时,它实际上就是2 ^ 24 * 2 ^ 8,即4294967000。加7只会添加到转换为ulong所截断的部分中。

如果将double更改为2 ^ 53的底数,它将适用于您想要的内容。

这可能是运行时问题,但在这种情况下,这是编译时问题,因为所有值都是常量,并且将由编译器求值。


-2

首先,您正在使用未经检查的上下文,这是对编译器的指令,作为开发人员,您确定结果不会溢出类型,并且您不希望看到编译错误。在您的方案中,您实际上是故意溢出类型的,并且期望在三个不同的编译器之间具有一致的行为,与新的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

我们从零开始就存在负一,这是很难自我减去的值。如果我对溢出的数学理解是正确的,我们将从最大值之后的下一个数字开始。

更新

根据jalsh的评论

7.0是双精度值,而不是浮点数,请尝试7.0f,它仍然会为您提供0

他的评论是正确的。如果我们使用float,则Roslyn和.NET Core仍为0,但另一方面,在7中使用double结果。

我做了一些额外的测试,结果变得更奇怪了,但最终一切都有意义(至少一点点)。

我假设的是.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

因此,最终,我相信导致不同行为的原因只是框架和/或编译器的不同版本,它们对常量表达式使用了不同的优化/计算,但是在其他情况下,行为却完全相同。


7.0是双精度值,而不是浮点数,请尝试7.0f,它仍然会为您提供0
jalsh

是的,它应该是浮点类型,而不是浮点型。感谢您的指正。
dropoutcoder

这就改变了问题的整体观点,当您将精度提高一倍时,您获得的精度要高得多,而V0ldek答案中解释的结果将大大改变,您可能只是将比例更改为两倍并再次检查,结果将是相同的。 ..
jalsh

最后,这是一个更复杂的问题。
dropoutcoder

1
@jalsh是的,但是有一个编译器标志可以将选中的上下文转到任何地方。您可能需要检查所有内容的安全性,但某些热路径需要它可以获取的所有CPU周期。
V0ldek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.