未チェックの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

私はかなり大きな違いのように見える行動の変化にもっと驚いています。そのキャストのチェーンtbhでも、「0」が有効な答えになるとは思いません。
Lukas

分かります。仕様のいくつかは、Roslynをビルドしたときにコンパイラーで修正されたため、その一部である可能性があります。SharpLab でこのバージョンの JIT出力を確認してください。これはキャストがulong結果にどのように影響するかを示しています。
madreflection

dotnetfiddleの例に戻ると、それは魅力的です。最後のWriteLineは、.NET Core 3.1のRoslyn 3.4および7で0を出力します
Lukas

デスクトップでも確認しました。JITコードもまったく似ていません。.NETCoreと.NET Frameworkで異なる結果が得られます。トリッピー
ルーカス

回答:


1

私の結論は正しくありませんでした。詳細については、更新を参照してください。

使用した最初のコンパイラのバグのようです。この場合、ゼロが正しい結果です。C#仕様で規定されている操作の順序は次のとおりです。

  1. を掛けscalescalea
  2. 実行するa + 7、譲るb
  3. キャストbulong、降伏c
  4. キャストcuint、降伏d

最初の2つの操作では、float値がのままになります b = 4.2949673E+09f。標準の浮動小数点演算では、これは 4294967296ここで確認できます)です。これはうまく収まるulongのでc = 4294967296、ですが、は1よりも大きい uint.MaxValueため、に往復するため0d = 0です。さて、驚きの驚き、浮動小数点演算は、ファンキーである、以来、4.2949673E+09fおよび4.2949673E+09f + 7IEEE 754そうでは正確に同じ番号がされてscale * scaleあなたの同じ値を与えるfloatようscale * scale + 7a = b第二の操作は基本的に何もしませんので、。

Roslynコンパイラーは、コンパイル時に(一部の)const操作を実行し、この式全体をに最適化し0ます。繰り返しますが、これは正しい結果であり、コンパイラーは、最適化を行わずに、最適化を行わなくてもコードとまったく同じ動作を実行できます。

私の推測では、使用した.NET 4.7.2コンパイラもこれを最適化しようとしますが、キャストを間違った場所で評価するバグがあります。あなたが最初のキャスト場合は当然、scaleuintしてから操作を行って、あなたが得る7ので、scale * scaleへのラウンドトリップ0とは、あなたが追加します7。しかし、これは、実行時に式を段階的に評価するときに得られる結果と一致しません。繰り返しになりますが、根本的な原因は生成された動作を調べたときの推測にすぎませんが、上記で述べたすべてを考慮すると、これは最初のコンパイラ側の仕様違反であると確信しています。

更新:

私は間抜けをしました。上記の答えを書いているときに私が知らなかったC#仕様このビットがあります:

浮動小数点演算は、演算の結果の型よりも高い精度で実行できます。たとえば、一部のハードウェアアーキテクチャは、doubleタイプよりも範囲と精度が高い「拡張」または「long double」浮動小数点タイプをサポートし、このより高い精度タイプを使用してすべての浮動小数点演算を暗黙的に実行します。このようなハードウェアアーキテクチャでは、パフォーマンスを犠牲にしてのみ、精度の低い浮動小数点演算を実行できます。C#では、パフォーマンスと精度の両方を失う実装を必要とせず、すべての浮動小数点演算に高精度の型を使用できます。 。より正確な結果を提供することを除いて、これが測定可能な影響を与えることはほとんどありません。ただし、x * y / zという形式の式では、

C#は、少なくとも IEEE 754のレベルで正確なレベルを提供する操作を保証しますが、必ずしも正確ではありません。これはバグではなく、仕様の機能です。ロザリンコンパイラは、正確にIEEE 754で規定されているように式を評価する権利であり、他のコンパイラが推測するその右にある2^32 + 77の中に入れましたuint

誤解を招く最初の答えをして申し訳ありませんが、少なくとも今日は全員が何かを学びました。


次に、現在の.NET Frameworkコンパイラにバグがあると思います(念のためにVS 2019で試したところです):)バグを記録する場所があるかどうかを確認しようと思いますが、そのようなものを修正しますおそらく多くの望ましくない副作用があり、おそらく無視されます...
Lukas

私はそれが時期尚早にintにキャストしているとは思わない、それは多くのケースではるかに明確な問題を引き起こしていたと思います中間値をフロートに格納する代わりに、それをスキップして、各式でそれを式自体に置き換えるだけです
jalsh

@jalsh私はあなたの推測を理解していないと思います。コンパイラが単純にそれぞれscaleを浮動小数点値に置き換え、実行時に他のすべてを評価した場合、結果は同じになります。詳しく説明できますか?
V0ldek

@ V0ldek、反対投票は間違いでした、私はそれを削除できるようにあなたの回答を編集しました:)
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への変換によって切り捨てられた部分にのみ追加されます

底が2 ^ 53であるdoubleに変更すると、必要に応じて機能します。

これは実行時の問題である可能性がありますが、この場合はすべての値が定数であり、コンパイラーによって評価されるため、コンパイル時の問題です。


-2

まず第一に、開発者として、結果が型をオーバーフローせず、コンパイルエラーが発生しないことをコンパイラに指示する、チェックされていないコンテキストを使用しています。あなたのシナリオでは、意図的にオーバーフロータイプにあり、3つの異なるコンパイラ間で一貫した動作を期待しています。これらのコンパイラの1つは、Roslynや.NET Coreと比較して、履歴との後方互換性が新しいものです。

もう1つは、暗黙的な変換と明示的な変換が混在していることです。Roslynコンパイラについてはわかりませんが、.NET Frameworkコンパイラと.NET Coreコンパイラでは、これらの操作に異なる最適化を使用している可能性があります。

ここでの問題は、コードの最初の行は浮動小数点値/型のみを使用しているが、2行目は浮動小数点値/型と整数値/型の組み合わせであることです。

整数浮動小数点型をすぐに作成する場合(7> 7.0)、3つのコンパイル済みソースすべてで非常に同じ結果が得られます。

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コンパイラにある可能性が最も高い」です。

信じられないもう1つの理由は、最初にチェックされていない計算結果の結果はすべて同じであり、UInt32型の最大値をオーバーフローする値であることです。

Console.WriteLine(unchecked((uint)(ulong)(1.2 * scale * scale + 1.5 * scale) - UInt32.MaxValue - 1)); // 859091763

マイナス1は、ゼロから開始するため存在します。これは、それ自体を減算するのが難しい値です。オーバーフローに関する数学の理解が正しければ、最大値の次の数値から始めます。

更新

jalshコメントによると

7.0はfloatではなくdoubleです。7.0fを試してください。それでも0になります

彼のコメントは正しい。floatを使用する場合、Roslynと.NET Coreには0が返されますが、7の結果は2倍になります。

私はいくつかの追加のテストを行い、さらに奇妙になりましたが、最終的にはすべてが(少なくとも少しは)理にかなっています。

私が想定しているのは、.NET Framework 4.7.2コンパイラ(2018年半ばにリリース)は.NET Core 3.1およびRoslyn 3.4コンパイラ(2019年末にリリース)とは異なる最適化を実際に使用していることです。これらのさまざまな最適化/計算は、コンパイル時に既知の定数値に対してのみ使用されます。uncheckedコンパイラーがオーバーフローの発生をすでに認識しているため、キーワードを使用する必要があったのはそのためですが、最終的なILを最適化するために異なる計算が使用されました。

IL_000a命令を除いて、同じソースコードとほぼ同じIL。1つのコンパイラが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はdoubleではなく、floatです。7.0fを試してください。それでも0
jalsh

はい、浮動小数点型ではなく、浮動小数点型でなければなりません。訂正ありがとうございます。
dropoutcoder

これにより、問題の全体的な見方が変わります。得られる精度を2倍に扱うと、はるかに高くなり、V0ldekの回答で説明されている結果が大幅に変わります。スケールを2倍に変更してもう一度チェックすると、結果は同じになります。 ..
jalsh

結局、それはより複雑な問題です。
dropoutcoder

1
@jalshはい。ただし、チェックされたコンテキストをどこでも有効にするコンパイラフラグがあります。取得できるすべてのCPUサイクルを必要とする特定のホットパスを除き、すべての安全性をチェックする必要がある場合があります。
V0ldek
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.