Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Konwersje między Decimal a binarnymi typami zmiennoprzecinkowymi oraz konwersje z BigInteger do binarnych typów zmiennoprzecinkowych dają teraz poprawnie zaokrąglone wyniki. Wcześniej te konwersje mogły obcinać, zaokrąglać wartości pośrednie lub odrzucać cyfry znaczące.
Wersja wprowadzona
.NET 11 (wersja zapoznawcza 7)
Poprzednie zachowanie
Konwersje z float i double do decimal zachowały tylko odpowiednio 7 i 15 znaczących cyfr dziesiętnych. Może to ukryć różnicę między binarnym literałem zmiennoprzecinkowym a literałem dziesiętnym:
using System.Globalization;
double value = 1.23;
decimal converted = (decimal)value;
Console.WriteLine(converted.ToString("G29", CultureInfo.InvariantCulture));
Dane wyjściowe:
1.23
Konwersje z decimal do double lub float mogą powodować więcej niż jedno zaokrąglenie. Przykład:
using System.Globalization;
decimal value = 10000000000000.099609375m;
double converted = (double)value;
Console.WriteLine(converted.ToString("G99", CultureInfo.InvariantCulture));
Dane wyjściowe:
10000000000000.09765625
Konwersje z decimal do Half lub BFloat16 po raz pierwszy przekonwertowały wartość na float, co może również spowodować niepoprawne zaokrąglenie wyniku.
Konwersje z BigInteger do double obcinały odrzucane bity zamiast zaokrąglać do najbliższej reprezentowalnej wartości. Przykład:
using System.Globalization;
using System.Numerics;
BigInteger value = long.MaxValue / 2;
double converted = (double)value;
Console.WriteLine(converted.ToString("G17", CultureInfo.InvariantCulture));
Dane wyjściowe:
4.6116860184273874E+18
Konwersje z BigInteger na float, Halflub BFloat16 po raz pierwszy przekonwertowały wartość na double, która może zaokrąglić dwukrotnie i wygenerować wynik jedną jednostkę w ostatnim miejscu z dala od najbliższej wartości reprezentującej.
Nowe zachowanie
Począwszy od wersji .NET 11 konwersje zaokrąglają dokładną wartość źródłową jednokrotnie do najbliższej reprezentowalnej wartości docelowej.
W pierwszym przykładzie wartość double nie jest dokładnie równa 1.23. Jego dokładna wartość wynosi około 1.229999999999999982236431605997..., więc przekonwertowana decimal wartość jest teraz:
1.229999999999999982236431606
W drugim przykładzie przekonwertowana double wartość to teraz:
10000000000000.099609375
Konwersje z decimal do BFloat16 lub Half są również poprawnie zaokrąglane i mogą powodować powstanie innego docelowego wzorca bitowego niż w poprzednich wersjach.
BigInteger Na przykład przekonwertowana double wartość to teraz:
4.6116860184273879E+18
Konwersje z BigInteger do float, Halflub BFloat16 są również poprawnie zaokrąglane.
Jeśli konwersja jest obliczana jako stała czasu kompilacji, kompilator hostowany przez zestaw SDK .NET 11 (wersja zapoznawcza 7) lub nowszy zestaw SDK może osadzić nowy wynik w zestawie wyjściowym podczas odbudowy projektu, niezależnie od struktury docelowej projektu.
Typ zmiany przełamującej
Ta zmiana jest zmianą behawioralną.
Przyczyna zmiany
Poprzednie algorytmy konwersji straciły informacje, które typ docelowy może reprezentować, a czasami wybrano wartość inną niż najbliższy wynik reprezentujący. Spowodowało to błędy precyzji przy konwersji decimal w obu kierunkach oraz przy konwersjach z BigInteger. Nowe algorytmy są zgodne z oczekiwaną zasadą arytmetyki zmiennoprzecinkowej: konwersja jest obliczana tak, jak gdyby z użyciem dokładnej pośredniej precyzji, po czym wynik jest jednokrotnie zaokrąglany do typu docelowego.
Aby uzyskać więcej informacji, zobacz dotnet/runtime#130565 i dotnet/runtime#130566.
Zalecana akcja
Nie zakładaj, że binarny literał zmiennoprzecinkowy i literał dziesiętny z tym samym tekstem źródłowym oznaczają tę samą wartość. Jeśli wartość ma być dziesiętna, użyj literału dziesiętnego:
decimal value = 123.4567m;
zamiast konwersji z literału double:
decimal value = (decimal)123.4567;
Aby przywrócić poprzedni wynik ogólny podczas konwersji na decimal, zaokrąglij przekonwertowaną wartość na 7 znaczących cyfr dziesiętnych dla float źródła lub 15 znaczących cyfr dziesiętnych dla double źródła. Poprzednia konwersja zaokrąglała do najbliższej wartości, a w przypadku remisu do liczby parzystej; nie obcinała. Wartości dodatnie i ujemne były obsługiwane symetrycznie.
Na przykład sformatowanie wartości źródłowej przy użyciu standardowego ciągu formatu liczbowego G7 lub G15 oraz przeanalizowanie wyniku powoduje odpowiednie zaokrąglenie do cyfr znaczących dla dowolnych skończonych wartości:
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
Implementacja oparta na typie Span nie powoduje alokacji pamięci. Wartości spoza zakresu decimal nadal powodują zgłoszenie wyjątku podczas parsowania, tak jak miało to miejsce podczas konwersji.
Zaktualizuj testy i zserializowane oczekiwane wartości, które odzwierciedlały poprzedni, nieprawidłowo zaokrąglony wynik. Obejmuje to kod, który zależał od obcinania wyniku konwersji BigInteger w kierunku zera. Jeśli aplikacja wymaga określonego przestarzałego wzorca bitów float, double, Half lub BFloat16 na potrzeby protokołu lub formatu pliku, jawnie zakoduj ten wzorzec bitów, zamiast odtwarzać go za pomocą konwersji liczbowej.
Nie ma przełącznika zgodności, aby przywrócić poprzednie algorytmy konwersji.
Interfejsy API, których dotyczy problem
- Decimal.Decimal(Single)
- Decimal.Decimal(Double)
- Jawne konwersje z Single i Double do Decimal
- Jawne konwersje z Decimal do Single i 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)i Decimal.CreateTruncating<TOther>(TOther) podczas konwersji na lub z Single lub Double
- Jawna konwersja z Decimal na Half
- Jawna konwersja z Decimal na BFloat16
- Jawne konwersje z BigInteger do Double, Single, Halfi BFloat16
- Konwersje równoważne wykonywane przez IConvertible lub przez generyczne interfejsy matematyczne