Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В .NET 11 непроверяемые преобразования из чисел с плавающей запятой в sbyte, byte, short, ushort и char теперь имеют поведение с насыщением на границах диапазона целевого типа. Значения, которые слишком малы или слишком большие, имеют минимальное или максимальное значение типа назначения соответственно.
Это изменение развивает изменение, внесённое в .NET 9 в преобразования чисел с плавающей запятой в целые, которое стандартизировало преобразования из float и double в int, uint, long и ulong. .NET 11 расширяет насыщенность до 8-и 16-разрядных назначений.
Это изменение распространяется на CoreCLR, включая его интерпретатор, и Native AOT. Mono не включен в это изменение.
Дополнительные сведения см. в статье dotnet/runtime#128604.
Представленная версия
.NET 11 предварительная версия 7
Предыдущее поведение
Ранее .NET не гарантировал результат непроверяемого преобразования числа с плавающей запятой в целочисленный тип, если значение выходило за диапазон целевого типа или было NaN. Результаты могут отличаться между реализациями среды выполнения, такими как CoreCLR и Mono, между архитектурами, такими как x86, x64, Arm32, Arm64 и WebAssembly, а также между наборами аппаратных инструкций в архитектуре, например x87, SSE2, AVX и AVX-512.
В критическом изменении .NET 9 были особо выделены x86 и x64, где преобразования при переполнении обычно возвращали сторожевые значения. Arm64 уже использовал насыщенные преобразования по соглашению. Это изменение стандартизировало преобразования к более широким целочисленным типам, а не закрепило старые сигнальные значения как часть контракта.
Для малых целочисленных типов типичная последовательность преобразования CoreCLR в .NET 9 и .NET 10 представляла собой преобразование с насыщением в int, с последующим сужением до целевого типа путем отбрасывания старших битов. Эта последовательность объясняет поведение, наблюдавшееся во многих приложениях, но не являлась гарантированным поведением при прямом приведении числа с плавающей запятой к целочисленному типу небольшого размера.
В следующей таблице показаны результаты этой двухфакторной последовательности для среды выполнения float или double значения x. Эти входные данные помещаются в int, поэтому примеры изолируют эффект отбрасывания всех битов, кроме младших 8 или 16 битов результата. Сохранённые биты интерпретируются как знаковые для sbyte и short, и как беззнаковые для byte, ushort и char.
char Результаты отображаются числовым образом.
| Преобразовать в | Значение параметра x |
Сохранённые низкие биты | Пример предыдущего результата |
|---|---|---|---|
sbyte или byte |
298 | 0x2A |
42 |
sbyte |
-298 | 0xD6 |
-42 |
byte |
-42 | 0xD6 |
214 |
short, ushort или char |
65578 | 0x002A |
42 |
short |
-65578 | 0xFFD6 |
-42 |
ushort или char |
-42 | 0xFFD6 |
65494 |
Например, следующий код может возвращать 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
Промежуточный int — 65578 (0x0001002A). Если сохраняются только младшие 16 бит, результат — 42 (0x002A), а не насыщение до short.MaxValue.
Новое поведение
Начиная с .NET 11, незаверенные преобразования насыщают границы целевого типа. Конечные значения в диапазоне назначения продолжают округляться до нуля.
NaN преобразуется в ноль.
| Преобразовать в | Ниже минимума, включая отрицательную бесконечность | Выше максимума, включая положительную бесконечность | 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 |
Предыдущий пример теперь возвращает 32767 (short.MaxValue) вместо 42. Аналогичным образом, преобразование из 298 в 255 теперь возвращает 42 вместо ushort, а преобразование из -42 в 0 теперь возвращает 65494 вместо byte.
Поскольку они преобразуются через float, соответствующие непроверяемые преобразования из Half также используют новое поведение.
ConvertToInteger<TInteger>(Single) и ConvertToInteger<TInteger>(Double) теперь корректно выполняют насыщение для этих небольших целевых типов.
Проверенные преобразования не изменяются и продолжают вызываться OverflowException при переполнении преобразования. Это изменение не затрагивает сужающие преобразования из одного целочисленного типа в другой, а также более широкие целочисленные и векторные преобразования, которые охватываются изменением в .NET 9.
Тип разрушающего изменения
Это изменение поведения.
Причина изменения
Изменение в .NET 9 ввело поведение с насыщением для преобразований в более широкие целочисленные типы, но преобразования в 8-битные и 16-битные целевые типы по-прежнему зависели от аппаратного обеспечения и реализации для значений вне диапазона и NaN. Это изменение придаёт этим преобразованиям детерминированное поведение с насыщением и обеспечивает совпадение предварительно инициализированных значений в JIT, интерпретаторе CoreCLR и Native AOT.
Рекомендуемое действие
Если код использует предыдущие результаты для входных данных вне диапазона, обновите его, чтобы ожидать насыщенности в границах конечного типа, где это возможно.
Если вам нужно специфичное для платформы поведение, которое обычно использовалось до этих изменений, самый простой обходной путь — ConvertToIntegerNative<TInteger>(Single) или ConvertToIntegerNative<TInteger>(Double). Например, замените прямое приведение (ushort)x на double.ConvertToIntegerNative<ushort>(x), если x — это double, или на float.ConvertToIntegerNative<ushort>(x), если это float.
Вы также можете явно выбрать промежуточное преобразование. В следующих примерах используются double в качестве входных данных x и ushort в качестве места назначения:
| Требуемое поведение | Conversion |
|---|---|
| Собственное для платформы преобразование в целевой тип, которое обычно восстанавливает прежнее поведение. | double.ConvertToIntegerNative<ushort>(x) |
Насыщение до int, затем сужение, в соответствии с общей последовательностью CoreCLR в .NET 9 и .NET 10 |
unchecked((ushort)(int)x) |
Преобразование в int, встроенное в платформу, с последующим сужением, что соответствует распространённой последовательности, использовавшейся до .NET 9 |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Используйте float.ConvertToIntegerNative для входных данных sbyte и замените соответствующий целевой тип для byte, char, short или float.
Как и при изменении .NET 9, ConvertToIntegerNativeне гарантируется воспроизведение предыдущих результатов для значений вне диапазона илиNaN. Выбирается поведение, эффективное для текущей платформы, которое может различаться в разных средах выполнения, на разных архитектурах или между ревизиями оборудования. Явное указание (ushort)(int)x вместо этого выбирает преобразование с насыщением в int с последующим целочисленным сужением; это не восстанавливает поведение всех исторических реализаций.
Если преобразованное значение используется в качестве индекса массива, смещения буфера или длины, убедитесь, что результирующее значение находится в обязательных границах. Преобразование, которое дает пригодное для использования значение на одной машине, не означает, что собственное для платформы преобразование даст такой же результат на другой.