Decimal ve BigInteger kayan noktalı dönüşümleri doğru şekilde yuvarlanır

Decimal ile ikili kayan noktalı türler arasındaki dönüştürmeler ve BigInteger öğesinden ikili kayan noktalı türlere yapılan dönüştürmeler artık doğru şekilde yuvarlatılmış sonuçlar üretir. Daha önce, bu dönüştürmeler kırpma yapabiliyor, ara değerler üzerinden yuvarlayabiliyor veya anlamlı basamakları atabiliyordu.

Sürüm kullanıma sunulmuştur

.NET 11 Önizleme 7

Önceki davranış

double ve decimal’den float’ye yapılan dönüştürmeler, sırasıyla yalnızca 7 ve 15 anlamlı ondalık basamağı korudu. Bu durum, ikili kayan noktalı sabit ile ondalık sabit arasındaki farkı gizleyebilir:

using System.Globalization;

double value = 1.23;
decimal converted = (decimal)value;

Console.WriteLine(converted.ToString("G29", CultureInfo.InvariantCulture));

Çıktı şöyleydi:

1.23

decimal türünden float veya double türüne dönüştürmeler birden fazla kez yuvarlanabilir. Örneğin:

using System.Globalization;

decimal value = 10000000000000.099609375m;
double converted = (double)value;

Console.WriteLine(converted.ToString("G99", CultureInfo.InvariantCulture));

Çıktı şu şekildeydi:

10000000000000.09765625

decimal'dan Half veya BFloat16'ye yapılan dönüştürmeler, önce değeri float'e dönüştürüyordu; bu da yanlış yuvarlanmış bir sonuca yol açabiliyordu.

double biçiminden BigInteger biçimine dönüştürmeler, en yakın temsil edilebilir değere yuvarlamak yerine atılan bitleri kırptı. Örneğin:

using System.Globalization;
using System.Numerics;

BigInteger value = long.MaxValue / 2;
double converted = (double)value;

Console.WriteLine(converted.ToString("G17", CultureInfo.InvariantCulture));

Çıktı şöyleydi:

4.6116860184273874E+18

BigInteger’dan float, Half veya BFloat16’e dönüşümler, önce değeri double’e dönüştürüyordu; bu da iki kez yuvarlamaya neden olarak son basamaktaki bir birim kadar en yakın temsil edilebilir değerden uzak bir sonuç üretebiliyordu.

Yeni davranış

.NET 11’den itibaren, dönüştürmeler tam kaynak değerini bir kez en yakın temsil edilebilen hedef değere yuvarlar.

İlk örnekte double değer tam olarak 1.23değildir. Tam değeri yaklaşık olarak 1.229999999999999982236431605997...değeridir, dolayısıyla dönüştürülen decimal değer şu şekildedir:

1.229999999999999982236431606

İkinci örnekte, dönüştürülen double değer şu şekildedir:

10000000000000.099609375

BFloat16’dan decimal veya Half’ye dönüştürmeler de doğru şekilde yuvarlanır ve önceki sürümlerdekinden farklı bir hedef bit deseni üretebilir.

BigInteger Örneğin, dönüştürülen double değer şu şekildedir:

4.6116860184273879E+18

BFloat16'den float'e, BigInteger'ye veya Half'e yapılan dönüşümler de doğru şekilde yuvarlanır.

Dönüştürme derleme zamanı sabiti olarak değerlendirilirse, .NET 11 Preview 7 SDK veya sonraki bir SDK tarafından barındırılan bir derleyici, projenin hedef çerçevesi ne olursa olsun proje yeniden oluşturulduğunda yeni sonucu çıkış derlemesine ekleyebilir.

Kesinti yaratan değişiklik türü

Bu değişiklik davranışsal bir değişikliktir.

Değişiklik nedeni

Önceki dönüştürme algoritmaları, hedef türün temsil edebileceği bilgileri kaybetti ve bazen en yakın temsil edilebilir sonuç dışında bir değer seçti. Bu durum, decimal dönüşümünün her iki yönünde ve BigInteger'den yapılan dönüşümlerde hassasiyet hatalarına neden oldu. Yeni algoritmalar, dönüştürmenin, ara hesaplamalarda tam duyarlık varmış gibi yapılıp ardından hedef türe tek bir kez yuvarlanmasına dayanan beklenen kayan noktalı hesaplama kuralını izler.

Daha fazla bilgi için bkz. dotnet/runtime#130565 ve dotnet/runtime#130566.

Kaynak metni aynı olan ikili kayan noktalı bir sabitin ve ondalık bir sabitin aynı değeri temsil ettiğini varsaymayın. Bir değerin ondalık olması amaçlanıyorsa, ondalık sabit değeri kullanın:

decimal value = 123.4567m;

double sabit değerinden dönüştürme yerine:

decimal value = (decimal)123.4567;

Genel olarak önceki sonucu geri yüklemek için, decimal biçimine dönüştürdüğünüzde dönüştürülen değeri, float kaynağı için 7 anlamlı basamağa veya double kaynağı için 15 anlamlı basamağa yuvarlayın. Bir önceki dönüştürme, en yakın değere, eşitlik durumunda çift sayıya yuvarladı; kesmedi. Pozitif ve negatif değerler simetrik olarak işlendi.

Örneğin, kaynak değerin G15 veya G7 standart sayısal biçim dizesi kullanılarak biçimlendirilmesi ve sonucun ayrıştırılması, rastgele sonlu değerler için karşılık gelen anlamlı basamaklara yuvarlama işlemini gerçekleştirir:

using System.Globalization;

static decimal ConvertToDecimalLikePrevious(float value)
{
    Span<char> text = stackalloc char[32];
    value.TryFormat(text, out int length, "G7", CultureInfo.InvariantCulture);
    return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture);
}

static decimal ConvertToDecimalLikePrevious(double value)
{
    Span<char> text = stackalloc char[32];
    value.TryFormat(text, out int length, "G15", CultureInfo.InvariantCulture);
    return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture);
}

decimal fromFloat = ConvertToDecimalLikePrevious(14.1f);          // 14.1
decimal fromDouble = ConvertToDecimalLikePrevious(-123.4567);     // -123.4567

Span tabanlı uygulama bellek ayırmaz. aralığı decimal dışındaki değerler, dönüştürme sırasında olduğu gibi ayrıştırma sırasında bir özel durum oluşturmaya devam eder.

Önceki, yanlış yuvarlanmış sonucu kodlayan testleri ve serileştirilmiş beklenen değerleri güncelleştirin. Buna, BigInteger dönüşümünde sıfıra doğru kesmeye bağlı olan kod da dahildir. Bir uygulama bir protokol veya BFloat16 dosya biçimi için belirli bir eski float, double, Halfveya bit deseni gerektiriyorsa, bu bit deseni sayısal dönüştürme yoluyla yeniden oluşturmak yerine açıkça kodlayın.

Önceki dönüştürme algoritmalarını geri yüklemek için uyumluluk anahtarı yoktur.

Etkilenen API'ler