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.
W .NET 11 niezaznaczone konwersje zmiennoprzecinkowe na sbyte, byte, short, ushorti char mają teraz zachowanie saturujące w granicach typu docelowego. Wartości, które są zbyt małe lub zbyt duże, są ustawione odpowiednio na wartość minimalną lub maksymalną typu docelowego.
Ta zmiana jest kontynuacją zmiany w platformie .NET 9 dotyczącej konwersji z typów zmiennoprzecinkowych na typy całkowite, która ujednoliciła konwersje z float i double na int, uint, long i ulong. .NET 11 rozszerza nasycenie do 8-i 16-bitowych miejsc docelowych.
Zmiana dotyczy CoreCLR, w tym jego interpretera i Native AOT. Mono nie jest uwzględniony w tej zmianie.
Aby uzyskać więcej informacji, zobacz dotnet/runtime#128604.
Wersja wprowadzona
.NET 11 (wersja zapoznawcza 7)
Poprzednie zachowanie
Wcześniej platforma .NET nie gwarantowała wyniku niekontrolowanej konwersji wartości zmiennoprzecinkowej na typ całkowity, gdy wartość powodowała przepełnienie typu docelowego lub wynosiła NaN. Wyniki mogą się różnić między implementacjami środowiska uruchomieniowego, takimi jak CoreCLR i Mono, między architekturami, takimi jak x86, x64, Arm32, Arm64 i WebAssembly, oraz między zestawami instrukcji sprzętowych w architekturze, takimi jak x87, SSE2, AVX i AVX-512.
Niezgodna zmiana w platformie .NET 9 dotyczyła w szczególności architektur x86 i x64, w których konwersje często zwracały wartości wartownicze w przypadku przepełnienia. Arm64 już używał konwersji saturujących zgodnie z konwencją. Zmiana znormalizowanych konwersji na szersze typy całkowite zamiast ustanawiania starych wyników sentinel jako kontraktu.
W przypadku małych typów całkowitych wspólna sekwencja konwersji CoreCLR w .NET 9 i .NET 10 była konwersją saturowaną na int, a następnie zawężeniem do typu docelowego przez odrzucenie dużych bitów. Ta sekwencja wyjaśnia zachowanie obserwowane w wielu aplikacjach, ale nie stanowiła gwarancji przy bezpośrednim rzutowaniu z typu zmiennoprzecinkowego na małoliczbowy typ całkowity.
Poniższa tabela przedstawia wyniki tej dwuetapowej sekwencji dla wartości x środowiska uruchomieniowego float lub double. Te wartości wejściowe mieszczą się w int, więc przykłady pokazują wpływ odrzucenia wszystkich bitów docelowych poza niższymi 8 lub 16 bitami. Zachowane bity są interpretowane jako liczby ze znakiem w przypadku sbyte i short, a jako liczby bez znaku w przypadku byte, ushort i char. Wyniki dla char są wyświetlane liczbowo.
| Konwertuj na | Wartość x |
Zachowane mniej znaczące bity | Przykładowy poprzedni wynik |
|---|---|---|---|
sbyte lub byte |
298 | 0x2A |
42 |
sbyte |
-298 | 0xD6 |
-42 |
byte |
-42 | 0xD6 |
214 |
short, ushort lub char |
65578 | 0x002A |
42 |
short |
-65578 | 0xFFD6 |
-42 |
ushort lub char |
-42 | 0xFFD6 |
65494 |
Na przykład poniższy kod może zwrócić 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
Pośredni int to 65578 (0x0001002A). Przy zachowaniu tylko niskich 16 bitów wynik to 42 (0x002A), a nie nasycenie do short.MaxValue.
Nowe zachowanie
Począwszy od platformy .NET 11 konwersje w kontekście unchecked są ograniczane do granic typu docelowego. Skończone wartości w docelowym przedziale nadal są zaokrąglane w kierunku zera.
NaN konwertuje na zero.
| Konwertuj na | Poniżej minimum, łącznie z nieskończonością ujemną | Powyżej maksimum, w tym nieskończoność dodatnia | NaN |
|---|---|---|---|
sbyte |
-128 (sbyte.MinValue) |
127 (sbyte.MaxValue) |
0 |
byte |
0 (byte.MinValue) |
255 (byte.MaxValue) |
0 |
short |
-32768 (short.MinValue) |
32767 (short.MaxValue) |
0 |
ushort |
0 (ushort.MinValue) |
65535 (ushort.MaxValue) |
0 |
char |
0 (char.MinValue) |
65535 (char.MaxValue) |
0 |
Powyższy przykład zwraca teraz 32767 (short.MaxValue) zamiast 42. Podobnie konwersja z 298 do byte teraz zwraca 255 zamiast 42, a konwersja z -42 do ushort teraz zwraca 0 zamiast 65494.
Ponieważ są konwertowane za pomocą float, odpowiednie niesprawdzone konwersje z Half również używają nowego zachowania.
ConvertToInteger<TInteger>(Single) i ConvertToInteger<TInteger>(Double) są teraz poprawnie nasycane dla tych mniejszych typów docelowych.
Sprawdzone konwersje są niezmienione i nadal są zgłaszane OverflowException , gdy konwersja przepełnia się. Ta zmiana nie wpływa na zawężające konwersje z typu całkowitego na typ całkowity ani na szersze konwersje typów całkowitych i wektorów objęte zmianą w platformie .NET 9.
Typ zmiany przełamującej
Ta zmiana jest zmianą behawioralną.
Przyczyna zmiany
Zmiana w .NET 9 wprowadziła saturacyjne działanie konwersji do typów całkowitych o większej szerokości, ale konwersje do 8-bitowych i 16-bitowych typów docelowych nadal miały zachowanie zależne od sprzętu i implementacji w przypadku wartości spoza zakresu oraz NaN. Ta zmiana nadaje tym konwersjom deterministyczne, nasycające zachowanie i sprawia, że wartości wstępnie zainicjowane w JIT, interpreterze CoreCLR i Native AOT są zgodne.
Zalecana akcja
Jeśli kod opiera się na wcześniejszych wynikach dla wartości wejściowych spoza zakresu, zaktualizuj go tak, aby tam, gdzie to możliwe, oczekiwał nasycenia do wartości granicznych typu docelowego.
Jeśli przed wprowadzeniem tych zmian potrzebujesz natywnego zachowania platformy, najprostszym obejściem jest ConvertToIntegerNative<TInteger>(Single) lub ConvertToIntegerNative<TInteger>(Double). Na przykład zastąp bezpośrednie rzutowanie (ushort)x elementem double.ConvertToIntegerNative<ushort>(x), gdy x jest typu double, lub elementem float.ConvertToIntegerNative<ushort>(x), gdy jest typu float.
Możesz również jawnie wybrać konwersję pośrednią. W poniższych przykładach użyto double wejścia x i ushort miejsca docelowego:
| Wymagane zachowanie | Conversion |
|---|---|
| Natywna dla platformy konwersja do typu docelowego, która zwykle przywraca wcześniejsze działanie | double.ConvertToIntegerNative<ushort>(x) |
Nasycenie do int, a następnie zawężenie, odpowiadające wspólnej sekwencji CoreCLR w .NET 9 i .NET 10 |
unchecked((ushort)(int)x) |
Natywna dla platformy konwersja do int, a następnie zawężanie, co odpowiada typowej sekwencji sprzed platformy .NET 9 |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Użyj float.ConvertToIntegerNative w przypadku danych wejściowych float i zastąp sbyte, byte, short lub char odpowiednim typem docelowym.
Podobnie jak w przypadku zmiany .NET 9, ConvertToIntegerNativenie ma gwarancji odtworzenia poprzednich wyników dla wartości poza zakresem lub NaN. Wybiera zachowanie wydajne dla bieżącej platformy, które może ulec zmianie w środowiskach uruchomieniowych, architekturach lub poprawkach sprzętu. Jawne (ushort)(int)x zamiast tego wybiera konwersję z nasyceniem do int, po której następuje zawężenie do typu całkowitego; nie przywraca ono zachowania wszystkich historycznych implementacji.
Jeśli przekonwertowana wartość jest używana jako indeks tablicy, przesunięcie buforu lub długość, sprawdź, czy wynikowa wartość znajduje się w wymaganych granicach. Konwersja, która na jednej maszynie daje wartość nadającą się do użycia, nie oznacza, że konwersja natywna dla platformy da taki wynik również na innej.
Interfejsy API, których dotyczy problem
- Niesprawdzane jawne rzutowania z Single lub Double do SByte, Byte, Int16, UInt16 lub Char.
- Half niezaznaczone jawne operatory konwersji:
-
ConvertToInteger<TInteger>(Single)gdy
TIntegerma wartość SByte, , Byte, Int16UInt16, lub Char. -
ConvertToInteger<TInteger>(Double)gdy
TIntegerma wartość SByte, , Byte, Int16UInt16, lub Char.