Převody typů Decimal a BigInteger na čísla s plovoucí desetinnou čárkou jsou správně zaokrouhlené.

Převody mezi Decimal a binárními typy s plovoucí řádovou čárkou a převody z BigInteger na binární typy s plovoucí řádovou čárkou nyní poskytují správně zaokrouhlené výsledky. Dříve tyto převody mohly zkrátit, zaokrouhlit mezilehlé hodnoty nebo zahodit významné číslice.

Verze byla představena

.NET 11 Preview 7

Předchozí chování

Převody z float a double na decimal zachovaly pouze 7 a 15 platných číslic. To by mohlo skrýt rozdíl mezi binárním literálem s plovoucí řádovou čárkou a desetinným literálem:

using System.Globalization;

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

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

Výstup byl:

1.23

Převody z decimal na float nebo double mohou být zaokrouhleny více než jednou. Příklady:

using System.Globalization;

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

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

Výstup byl:

10000000000000.09765625

Převody z decimal na Half nebo BFloat16 nejprve převedly hodnotu na float, což mohlo také vést k nesprávně zaokrouhlenému výsledku.

Při převodu z BigInteger na double byly odřezané bity zahazovány místo zaokrouhlení na nejbližší reprezentovatelnou hodnotu. Příklady:

using System.Globalization;
using System.Numerics;

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

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

Výstup byl:

4.6116860184273874E+18

Převody z BigInteger na float, Half nebo BFloat16 nejprve převedly hodnotu na double, což mohlo způsobit dvojí zaokrouhlení a vést k výsledku vzdálenému o jednu jednotku na posledním místě od nejbližší reprezentovatelné hodnoty.

Nové chování

Počínaje .NET 11 převody zaokrouhlují přesnou zdrojovou hodnotu jednou na nejbližší reprezentovatelnou cílovou hodnotu.

V prvním příkladu hodnota double není přesně 1.23. Jeho přesná hodnota je přibližně 1.229999999999999982236431605997..., takže převedená decimal hodnota je nyní:

1.229999999999999982236431606

V druhém příkladu je převedená double hodnota nyní:

10000000000000.099609375

Převody z decimal do Half nebo BFloat16 jsou také správně zaokrouhleny a mohou vytvořit jiný cílový bitový vzor než v předchozích verzích.

BigInteger Například převedená double hodnota je teď:

4.6116860184273879E+18

Převody z BigInteger na float, Halfnebo BFloat16 jsou také správně zaokrouhleny.

Pokud se převod vyhodnotí jako konstanta kompilátoru, kompilátor hostovaný sadou .NET 11 Preview 7 SDK nebo novější sadou SDK může vložit nový výsledek do výstupního sestavení při novém vytvoření projektu bez ohledu na cílovou architekturu projektu.

Typ zásadní změny

Tato změna je změna chování.

Důvod změny

Předchozí převodní algoritmy ztratily informace, které by cílový typ mohl představovat a někdy vybrali jinou hodnotu než nejbližší reprezentovatelný výsledek. To způsobilo chyby přesnosti v obou směrech převodu decimal a při převodech z BigInteger. Nové algoritmy dodržují očekávané pravidlo pro výpočet převodu v plovoucí řádové čárce, jako by se používala přesná mezivýsledná přesnost, po níž následuje jediné zaokrouhlení na cílový typ.

Další informace najdete v tématu dotnet/runtime#130565 a dotnet/runtime#130566.

Nepředpokládáme, že binární literál s plovoucí desetinnou čárkou a desetinný literál se stejným zdrojovým textem představuje stejnou hodnotu. Pokud má být hodnota desetinná, použijte desetinný literál:

decimal value = 123.4567m;

místo převodu z literálu double:

decimal value = (decimal)123.4567;

Pokud chcete obnovit předchozí výsledek obecně při převodu na decimal, zaokrouhlete převedenou hodnotu na 7 významných desetinných míst pro float zdroj nebo 15 desetinných číslic pro double zdroj. Předchozí převod zaokrouhloval na nejbližší hodnotu, v případě shody k sudému číslu; nešlo o oříznutí. Kladné a záporné hodnoty byly zpracovány symetricky.

Například formátování zdrojové hodnoty pomocí G7G15 standardního řetězce číselného formátu a analýza výsledku provede odpovídající zaokrouhlení významných číslic pro libovolné konečné hodnoty:

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

Implementace založená na rozsahu se nepřiděluje. Hodnoty mimo rozsah decimal nadále vyvolávají výjimku při parsování, stejně jako při převodu.

Aktualizujte testy a serializované očekávané hodnoty, které kódovaly předchozí nesprávně zaokrouhlený výsledek. To zahrnuje kód, který spoléhal na BigInteger převod se zkrácením směrem k nule. Pokud aplikace pro protokol nebo formát souboru vyžaduje konkrétní starší bitový vzor BFloat16, float, double nebo Half, zakódujte tento bitový vzor explicitně, místo jeho reprodukce pomocí číselné konverze.

Neexistuje žádný přepínač kompatibility pro obnovení předchozích převodních algoritmů.

Ovlivněná rozhraní API