Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Konverteringar mellan Decimal och binära flyttalstyper och konverteringar från BigInteger till binära flyttalstyper ger nu korrekt avrundade resultat. Tidigare kunde dessa konverteringar trunkera, avrunda genom mellanliggande värden eller ta bort betydande siffror.
Version lanserad
.NET 11 Förhandsversion 7
Tidigare beteende
Konverteringar från float och double till decimal behöll endast 7 respektive 15 signifikanta siffror. Detta kan dölja skillnaden mellan en binär flyttalsliteral och en decimalliteral:
using System.Globalization;
double value = 1.23;
decimal converted = (decimal)value;
Console.WriteLine(converted.ToString("G29", CultureInfo.InvariantCulture));
Utdata var:
1.23
Konverteringar från decimal till float eller double kan avrundas mer än en gång. Ett exempel:
using System.Globalization;
decimal value = 10000000000000.099609375m;
double converted = (double)value;
Console.WriteLine(converted.ToString("G99", CultureInfo.InvariantCulture));
Utdata var:
10000000000000.09765625
Konverteringar från decimal till Half eller BFloat16 konverterade först värdet till float, vilket också kan ge ett felaktigt avrundat resultat.
Konverteringar från BigInteger till double kapade bort bitar i stället för att avrunda till närmaste representerbara värde. Ett exempel:
using System.Globalization;
using System.Numerics;
BigInteger value = long.MaxValue / 2;
double converted = (double)value;
Console.WriteLine(converted.ToString("G17", CultureInfo.InvariantCulture));
Utdata var:
4.6116860184273874E+18
Konverteringar från BigInteger till float, Halfeller BFloat16 konverterade först värdet till double, som kunde avrunda två gånger och generera ett resultat en enhet på sista plats bort från närmaste representerande värde.
Nytt beteende
Från och med .NET 11 avrundar konverteringar det exakta källvärdet en gång till närmaste målvärde som kan representeras.
För det första exemplet double är värdet inte exakt 1.23. Dess exakta värde är ungefär 1.229999999999999982236431605997..., så det konverterade decimal värdet är nu:
1.229999999999999982236431606
För det andra exemplet är det konverterade double värdet nu:
10000000000000.099609375
Konverteringar från decimal till Half eller BFloat16 är också korrekt avrundade och kan ge ett annat målbitmönster än i tidigare versioner.
I exemplet BigInteger är det konverterade double värdet nu:
4.6116860184273879E+18
Konverteringar från BigInteger till float, Halfeller BFloat16 är också korrekt avrundade.
Om en konvertering utvärderas som en kompileringskonstant kan en kompilator som hanteras av .NET 11 Preview 7 SDK eller ett senare SDK bädda in det nya resultatet i utdatasammansättningen när projektet återskapas, oavsett projektets målramverk.
Typ av brytande ändring
Den här ändringen är en beteendeförändring.
Orsak till ändringen
De tidigare konverteringsalgoritmerna förlorade information som måltypen kunde representera och valde ibland ett annat värde än det närmaste representerande resultatet. Detta orsakade precisionsfel i båda konverteringsriktningarna decimal och i konverteringar från BigInteger. De nya algoritmerna följer den förväntade regeln för flyttalsberäkning för hur konverteringen beräknas, som om den utfördes med exakt mellanliggande precision, och därefter en enda avrundning till måltypen.
Mer information finns i dotnet/runtime#130565 och dotnet/runtime#130566.
Rekommenderad åtgärd
Anta inte att en binär flyttalsliteral och en decimalliteral med samma källtext representerar samma värde. Om ett värde är avsett att vara ett decimaltal, använder du en decimalliteral:
decimal value = 123.4567m;
i stället för en konvertering från en double litteral:
decimal value = (decimal)123.4567;
Om du vill återställa det tidigare resultatet i allmänhet när du konverterar till decimalavrundar du det konverterade värdet till 7 signifikanta decimalsiffror för en float källa eller 15 signifikanta decimalsiffror för en double källa. Den tidigare konverteringen avrundade till närmaste med mittpunktsavrundning till jämnt tal; den kapade inte. Positiva och negativa värden hanterades symmetriskt.
Till exempel ger formatering av källvärdet med standardformatsträngen G15 eller G7 för numeriska värden och tolkning av resultatet motsvarande avrundning till ett visst antal värdesiffror för godtyckliga finita värden:
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
Den span-baserade implementeringen allokerar inte. Värden utanför intervallet decimal fortsätter att generera ett undantag vid parsning, precis som de gjorde vid konvertering.
Uppdatera tester och serialiserade förväntade värden som kodade det tidigare, felaktigt avrundade resultatet. Detta inkluderar kod som var BigInteger beroende av konverteringstrunkering mot noll. Om ett program kräver ett specifikt äldre float, double, Halfeller BFloat16 bitmönster för ett protokoll eller filformat, kodar du det bitmönstret explicit i stället för att återskapa det via en numerisk konvertering.
Det finns ingen kompatibilitetsväxling för att återställa tidigare konverteringsalgoritmer.
Berörda API:er
- Decimal.Decimal(Single)
- Decimal.Decimal(Double)
- Explicita konverteringar från Single och Double till Decimal
- Explicita konverteringar från Decimal till Single och Double
- Decimal.ToSingle(Decimal)
- Decimal.ToDouble(Decimal)
- Convert.ToDecimal(Single)
- Convert.ToDecimal(Double)
- Convert.ToSingle(Decimal)
- Convert.ToDouble(Decimal)
- Decimal.CreateChecked<TOther>(TOther), Decimal.CreateSaturating<TOther>(TOther)och Decimal.CreateTruncating<TOther>(TOther) när de konverterar till eller från Single eller Double
- Explicit konvertering från Decimal till Half
- Explicit konvertering från Decimal till BFloat16
- Explicita konverteringar från BigInteger till Double, Single, Halfoch BFloat16
- Motsvarande konverteringar som utförs via IConvertible eller de allmänna matematiska gränssnitten