Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En .NET 11, las conversiones no comprobadas de punto flotante a sbyte, byte, short, ushort y char ahora tienen un comportamiento de saturación en los límites del tipo de destino. Los valores que son demasiado pequeños o demasiado grandes se establecen en el valor mínimo o máximo del tipo de destino, respectivamente.
Este cambio continúa el cambio de .NET 9 en las conversiones de coma flotante a enteros, que estandarizó las conversiones de float y double a int, uint, long y ulong. .NET 11 extiende la saturación a destinos de 8 y 16 bits.
El cambio se aplica a CoreCLR, incluido su intérprete y AOT nativo. Mono no se incluye en este cambio.
Para obtener más información, vea dotnet/runtime#128604.
Versión introducida
.NET 11 Preview 7
Comportamiento anterior
Anteriormente, .NET no garantizaba el resultado de una conversión sin comprobación de punto flotante a un tipo entero cuando el valor desbordaba el tipo de destino o era NaN. Los resultados podrían diferir entre las implementaciones en tiempo de ejecución, como CoreCLR y Mono, entre arquitecturas, como x86, x64, Arm32, Arm64 y WebAssembly, y entre conjuntos de instrucciones de hardware dentro de una arquitectura, como x87, SSE2, AVX y AVX-512.
El cambio importante de .NET 9 destacó específicamente x86 y x64, donde las conversiones solían devolver valores centinela en caso de desbordamiento. Arm64 ya ha usado conversiones de saturación por convención. El cambio estandarizó las conversiones a los tipos enteros de mayor tamaño en lugar de establecer los antiguos resultados centinela como un comportamiento garantizado.
Para los tipos enteros pequeños, una secuencia de conversión de CoreCLR común en .NET 9 y .NET 10 era una conversión saturante a int, seguida de restringir al tipo de destino descartando los bits altos. Esta secuencia explica el comportamiento que experimentaron muchas aplicaciones, pero no constituía una garantía para una conversión directa de coma flotante a un tipo entero de pequeño tamaño.
En la tabla siguiente se muestran los resultados de esa secuencia de dos pasos para un tiempo de ejecución float o double un valor x. Estas entradas caben en int, por lo que los ejemplos aíslan el efecto de descartar todos los bits inferiores a 8 o 16 bits del destino. Los bits retenidos se interpretan como firmados para sbyte y short, y sin signo para byte, ushorty char. Los resultados de char se muestran numéricamente.
| Convertir en | Valor de x |
Bits menos significativos conservados | Resultado anterior de ejemplo |
|---|---|---|---|
sbyte o byte |
298 | 0x2A |
42 |
sbyte |
-298 | 0xD6 |
-42 |
byte |
-42 | 0xD6 |
214 |
short, ushort o char |
65578 | 0x002A |
42 |
short |
-65578 | 0xFFD6 |
-42 |
ushort o char |
-42 | 0xFFD6 |
65494 |
Por ejemplo, el código siguiente podría devolver 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
El intermedio int es 65578 (0x0001002A). Al conservarse solo los 16 bits menos significativos, el resultado es 42 (0x002A), en lugar de saturarse a short.MaxValue.
Nuevo comportamiento
A partir de .NET 11, las conversiones sin comprobación se saturan en los valores límite del tipo de destino. Los valores finitos dentro del intervalo de destino siguen redondeándose hacia cero.
NaN convierte en cero.
| Convertir en | Por debajo del mínimo, incluido el infinito negativo | Encima del máximo, incluido el infinito positivo | 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 |
El ejemplo anterior ahora devuelve 32767 (short.MaxValue) en lugar de 42. De forma similar, una conversión de 255 a 42 ahora devuelve -42 en lugar de ushort, y una conversión de 0 a 65494 ahora devuelve byte en lugar de 298.
Dado que se convierten a través de float, las correspondientes conversiones no comprobadas de Half también usan el nuevo comportamiento.
ConvertToInteger<TInteger>(Single) y ConvertToInteger<TInteger>(Double) ahora saturan correctamente estos tipos de destino pequeños.
Las conversiones verificadas siguen sin cambios y continúan lanzando OverflowException cuando la conversión produce un desbordamiento. Este cambio no modifica las conversiones de restricción de enteros a enteros ni las conversiones de enteros y vectores más amplias cubiertas por el cambio de .NET 9.
Tipo de cambio disruptivo
Este es un cambio de comportamiento.
Motivo del cambio
El cambio de .NET 9 estableció un comportamiento de saturación para las conversiones a tipos enteros de mayor tamaño, pero las conversiones a tipos de destino de 8 y 16 bits seguían teniendo un comportamiento dependiente del hardware y de la implementación para los valores fuera de rango y NaN. Este cambio proporciona a esas conversiones un comportamiento determinista y con saturación, y hace que coincidan los valores preinicializados del JIT, del intérprete de CoreCLR y de Native AOT.
Acción recomendada
Si el código se basa en resultados previos para valores de entrada fuera de rango, actualícelo para prever una saturación en los límites del tipo de destino siempre que sea posible.
Si necesita el comportamiento nativo de la plataforma que se usa habitualmente antes de estos cambios, la solución alternativa más sencilla es ConvertToIntegerNative<TInteger>(Single) o ConvertToIntegerNative<TInteger>(Double). Por ejemplo, reemplace una conversión directa (ushort)x por double.ConvertToIntegerNative<ushort>(x) cuando x es un double, o float.ConvertToIntegerNative<ushort>(x) cuando lo es un float.
También puede seleccionar explícitamente la conversión intermedia. En los ejemplos siguientes se usa una double entrada x y un ushort destino:
| Comportamiento requerido | Conversion |
|---|---|
| Conversión nativa de plataforma al tipo de destino, que normalmente recupera el comportamiento anterior | double.ConvertToIntegerNative<ushort>(x) |
Saturación hasta int, y luego reducción, en consonancia con la secuencia común de CoreCLR en .NET 9 y .NET 10 |
unchecked((ushort)(int)x) |
Conversión nativa de la plataforma a int y, a continuación, reducción, siguiendo una secuencia común anterior a .NET 9 |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Use float.ConvertToIntegerNative para float las entradas y sustituya el tipo de destino adecuado para sbyte, byte, shorto char.
Al igual que con el cambio de .NET 9, ConvertToIntegerNative no se garantiza que reproduzca los resultados anteriores para los valores fuera del intervalo o NaN. Selecciona el comportamiento que es eficaz para la plataforma actual, que puede cambiar en tiempo de ejecución, arquitecturas o revisiones de hardware. En cambio, un (ushort)(int)x explícito selecciona una conversión con saturación a int seguida de una reducción a un tipo entero más estrecho; no restaura el comportamiento de todas las implementaciones históricas.
Si el valor convertido se usa como índice de matriz, desplazamiento del búfer o longitud, valide que el valor resultante esté dentro de los límites necesarios. Una conversión que genera un valor utilizable en una máquina no establece que la conversión nativa de la plataforma lo hará en otro.
Las APIs afectadas
- Conversiones explícitas sin comprobación de Single o Double a SByte, Byte, Int16, UInt16 o Char.
- Half Operadores de conversión explícitos desactivados:
-
ConvertToInteger<TInteger>(Single) cuando
TIntegeres SByte, Byte, Int16, UInt16o Char. -
ConvertToInteger<TInteger>(Double) cuando
TIntegeres SByte, Byte, Int16, UInt16o Char.