C # denetlenmeyen uint için taşma davranışı


10

Bu kodu https://dotnetfiddle.net/ adresinde test ediyorum :

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 ile derlersem,

859091763

7

Ama eğer Roslyn veya .NET Core yaparsam,

859091763

0

Bu neden oluyor?


Şu durumda, oyuncu kadrosu ulongson durumda yok sayılıyor, bu yüzden float-> intdönüşümünde oluyor .
madreflection

Davranış değişikliğinden çok şaşırdım, bu oldukça büyük bir fark gibi görünüyor. "0" ın da o hileler zinciri ile geçerli bir cevap olmasını beklemezdim.
Lukas

Anlaşılabilir. Spesifikasyondaki birkaç şey, Roslyn'i inşa ettiklerinde derleyicide düzeltildi, böylece bunun bir parçası olabilir. SharpLab üzerinde bu sürümdeki JIT çıktısını kontrol edin . Bu, oyuncuların ulongsonucu nasıl etkilediğini gösterir .
madreflection

Dotnetfiddle'daki örneğinizle büyüleyici, son WriteLine Roslyn 3.4'te 0 ve .NET Core 3.1'de 7 çıktı
Lukas

Masaüstümde de onayladım. JIT kodu bile hiç yakın görünmüyor, .NET Core ve .NET Framework arasında farklı sonuçlar elde ediyorum. Trippy
Lukas

Yanıtlar:


1

Çıkarımlar yanlıştı. Daha fazla ayrıntı için güncellemeye bakın.

Kullandığınız ilk derleyicide bir hata gibi görünüyor. Bu durumda sıfır doğru sonuçtur . C # belirtimi ile dikte edilen işlem sırası aşağıdaki gibidir:

  1. çarpın scaletarafından scaleverimli,a
  2. gerçekleştirmek a + 7, vermekb
  3. oyuncular b için ulong, sonuçtac
  4. dökme ciçinuint, sonuçtad

İlk iki işlem sizi şamandıra değeri ile bırakır b = 4.2949673E+09f. Standart kayan nokta aritmetiği altında bu 4294967296( buradan kontrol edebilirsiniz ). İçine uyan O ulongsadece ince, bu nedenle c = 4294967296, ancak mutlaka bir aşkın var uint.MaxValueiçin o yüzden yuvarlak gezileri, 0dolayısıyla d = 0. Şimdi, sürpriz sürpriz, çünkü kayan nokta aritmetiği korkak 4.2949673E+09fve 4.2949673E+09f + 7IEEE 754'te aynı sayıdır. Yani scale * scalesize aynı değeri verecektir.float olarak scale * scale + 7, a = bikinci operasyonlar temelde bir no-op yani.

Roslyn derleyicisi, derleme zamanında (bazı) const işlemleri gerçekleştirir ve tüm ifadeyi buna göre optimize eder 0. Yine, bu doğru sonuçtur ve derleyicinin, onlarsız kodla aynı davranışla sonuçlanacak herhangi bir optimizasyon yapmasına izin verilir.

Benim tahminim kullandığınız .NET 4.7.2 derleyicisi de bunu optimize etmeye çalışır, ancak almayı yanlış bir yerde değerlendirmesine neden olan bir hataya sahiptir. İlk atama, Doğal olarak, scalebir karşı uintve daha sonra işlemi gerçekleştirmek elde edersiniz 7çünkü, scale * scalegidiş-gezileri 0ve eklemek 7. Ancak bu, ifadeleri adım adım değerlendirirken alacağınız sonuçla tutarsızdır . Yine, temel neden üretilen davranışa bakarken sadece bir tahmindir, ancak yukarıda belirttiğim her şey göz önüne alındığında, bunun ilk derleyicinin yanında bir spec ihlali olduğuna ikna oldum.

GÜNCELLEME:

Ben bir aptallık yaptım. Orada C # şartnamenin bu biraz yukarıda cevabı yazarken ben bilmediğim o:

Kayan nokta işlemleri, işlemin sonuç türünden daha yüksek bir hassasiyetle gerçekleştirilebilir. Örneğin, bazı donanım mimarileri, çift tipten daha geniş bir aralık ve hassasiyetle "genişletilmiş" veya "uzun çift" kayan nokta tipini destekler ve bu yüksek hassasiyetli türü kullanarak tüm kayan nokta işlemlerini dolaylı olarak gerçekleştirir. Bu tür donanım mimarileri, kayan nokta işlemlerini daha az hassasiyetle gerçekleştirmek için yalnızca aşırı yüksek maliyetle yapılabilir ve hem performans hem de kesinlikten vazgeçmek için bir uygulama gerektirmez, C #, tüm kayan nokta işlemleri için daha yüksek bir hassasiyet türünün kullanılmasına izin verir . Daha kesin sonuçlar vermekten başka, bunun nadiren ölçülebilir etkileri vardır. Ancak, x * y / z formunun ifadelerinde,

C #, en azından IEEE 754 seviyesinde bir hassasiyet seviyesi sağlamak için operasyonları garanti eder , ancak tam olarak böyle değildir . Bu bir hata değil, bir özelliktir. Roslyn derleyici tam IEEE 754 belirttiği olarak ifade değerlendirmek için kendi başlarına ve diğer derleyici anlamak için onun sağ olduğunu 2^32 + 7edilir 7koymak uint.

Yanıltıcı ilk cevabım için üzgünüm, ama en azından bugün hepimiz bir şeyler öğrendik.


Sonra sanırım şu anki .NET Framework derleyicisinde bir hata var (sadece emin olmak için VS 2019'da denedim) :) Sanırım böyle bir şeyi düzeltirken bir hatayı günlüğe kaydetmek için bir yer olup olmadığını görmeye çalışacağım muhtemelen istenmeyen birçok yan etkisi vardır ve muhtemelen göz ardı edilir ...
Lukas

Ben erken int için döküm sanmıyorum, bu bir sürü durumda çok daha açık sorunlara neden olurdu, sanırım burada durum const operasyonda değeri değerlendirmek ve en son kadar döküm değil, yani şamandıralarda ara değerleri saklamak yerine, sadece bunu atlamak ve her ifadedeki ifadeyi ifadenin kendisiyle değiştirmek
jalsh

@jalsh Sanırım tahminini anladığımı sanmıyorum. Derleyici her birini scalefloat değeriyle değiştirdiyse ve daha sonra çalışma zamanında diğer her şeyi değerlendirdiyse, sonuç aynı olacaktır. Detaylandırabilir misin?
V0ldek

@ V0ldek, downvote bir hataydı, cevabınızı düzenledim, böylece kaldırabilirim :)
jalsh

benim tahminim aslında ara değerleri kayan noktalarda saklamamış, sadece f'yi float'a dökmeden f'yi değerlendiren ifadeyle değiştirmiş.
f'yi kayana çevirmeden f'yi

0

Buradaki nokta ( dokümanlarda gördüğünüz gibi ) kayan değerlerin yalnızca 2 ^ 24'e kadar bir tabanı olabileceğidir . Böylece, 2 ^ 32 ( 64 * 2014 * 164 * 1024 = 2 ^ 6 * 2 ^ 10 * 2 ^ 6 * 2 ^ 10 = 2 ^ 32 ) değeri atadığınızda , aslında 2 ^ 24 * 2 ^ olur 8 , olan 4294967000 . 7 ekleme yalnızca ulong biçimine dönüştürülerek kesilen parçaya eklenecektir .

Tabanı 2 ^ 53 olan iki katı olarak değiştirirseniz , istediğiniz şey için çalışır.

Bu bir çalışma zamanı sorunu olabilir, ancak bu durumda, derleme zamanı sorunudur, çünkü tüm değerler sabittir ve derleyici tarafından değerlendirilir.


-2

Her şeyden önce, bir geliştirici olarak, sonucun türü taşmayacağından ve herhangi bir derleme hatası görmek istemediğinizden emin olduğunuz derleyici için bir talimat olan kontrolsüz bağlam kullanıyorsunuz. Senaryonuzda aslında taşan tipte ve bunlardan biri muhtemelen yeni olan Roslyn ve .NET Core'a kıyasla geçmişle geriye dönük uyumlu olan üç farklı derleyici arasında tutarlı bir davranış bekliyorsunuz.

İkinci şey, örtük ve açık dönüşümleri karıştırmanızdır. Roslyn derleyicisi hakkında emin değilim, ama kesinlikle .NET Framework ve .NET Core derleyicileri bu işlemler için farklı optimizasyonlar kullanabilir.

Buradaki sorun, kodunuzun ilk satırının yalnızca kayan nokta değerleri / türleri kullanmasıdır, ancak ikinci satır kayan nokta değerleri / türleri ve integral değeri / türünün birleşimidir.

Tam sayı kayan nokta türünü hemen yaparsanız (7> 7,0) derlenmiş üç kaynağın tümü için aynı sonucu alırsınız.

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
    }
}

Yani, ben V0ldek cevap ne ters olduğunu söyleyebilirim ve bu "hata (gerçekten bir hata ise) Roslyn ve .NET Core derleyiciler büyük olasılıkla".

Buna inanmanın bir başka nedeni, ilk kontrolsüz hesaplama sonuçlarının herkes için aynı olması ve maksimum tür değerinden taşan değerdir UInt32.

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

Eksi bir var, sıfırdan başlıyoruz, ki bu kendini çıkarmak zor bir değerdir. Matematik taşma anlayışım doğruysa, maksimum değerden sonraki rakamdan başlarız.

GÜNCELLEME

Jalsh yorumuna göre

7.0 bir çift değil, bir kayan nokta değil, 7.0f'yi deneyin, yine de size 0 verir

Onun yorumu doğrudur. Float kullanmamız durumunda, Roslyn ve .NET Core için 0 elde edersiniz, ancak diğer yandan 7'de çift sonuç kullanırsınız.

Bazı ek testler yaptım ve işler daha da tuhaflaştı, ama sonunda her şey mantıklı (en azından biraz).

Benim varsaydığım şey, .NET Framework 4.7.2 derleyicisinin (2018'in ortalarında piyasaya sürüldü) gerçekten .NET Core 3.1 ve Roslyn 3.4 derleyicilerinden (2019'un sonunda piyasaya sürüldü) farklı optimizasyonlar kullanmasıdır. Bu farklı optimizasyonlar / hesaplamalar sadece derleme zamanında bilinen sabit değerler için kullanılır. Bu yüzden kullanmaya ihtiyaç vardıunchecked derleyici taşma olduğunu zaten biliyor, ancak son IL optimize etmek için farklı hesaplama anahtar kelime vardı.

IL_000a komutu dışında aynı kaynak kodu ve neredeyse aynı IL. Bir derleyici 7 ve diğer 0'ı hesaplar.

Kaynak kodu

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 Derleyici Şubesi (Eyl 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

uncheckedAşağıdaki gibi sabit olmayan ifadeler (varsayılan olarak ) eklediğinizde doğru yoldan gitmeye başlar .

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
    }
}

Her iki derleyici tarafından "tam olarak" aynı IL üretir.

.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 Derleyici Şubesi (Eyl 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

Sonuçta, farklı davranışların nedeninin, sabit ifadeler için farklı optimizasyonlar / hesaplama kullanan farklı bir çerçeve ve / veya derleyici sürümü olduğuna inanıyorum, ancak diğer durumlarda davranış çok aynı.


7.0 bir çift değil, bir kayan nokta değil, 7.0f deneyin, yine de size 0
jalsh

Evet, kayan nokta tipi olmalı, kayan değil. Düzeltme için teşekkürler.
dropoutcoder

Bu, sorunun tüm perspektifini değiştirir, elde ettiğiniz bir çifte hassasiyetle uğraşırken çok daha yüksek olur ve V0ldek'in cevabında açıklanan sonuç büyük ölçüde değişir, ölçeği iki katına ve tekrar kontrol etmeyi tercih edebilirsiniz, sonuçlar aynı olacaktır. ..
jalsh

Sonunda daha karmaşık bir mesele.
dropoutcoder

1
@jalsh Evet, ancak işaretli içeriği her yere çeviren bir derleyici bayrağı var. Alabileceği tüm CPU döngülerine ihtiyaç duyan belirli bir sıcak yol haricinde her şeyi güvenlik açısından kontrol ettirmek isteyebilirsiniz.
V0ldek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.