Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Em .NET 11, conversões de ponto flutuante sem verificação para sbyte, byte, short, ushort e char agora têm comportamento saturante nos limites do tipo de destino. Os valores muito pequenos ou muito grandes são definidos como o valor mínimo ou máximo do tipo de destino, respectivamente.
Esta alteração dá continuidade à alteração do .NET 9 nas conversões de ponto flutuante para inteiro, que padronizou as conversões de float e double para int, uint, long e ulong. .NET 11 estende a saturação para destinos de 8 e 16 bits.
A alteração se aplica ao CoreCLR, incluindo seu interpretador e AOT nativo. Mono não está incluído nessa alteração.
Para obter mais informações, consulte dotnet/runtime#128604.
Versão introduzida
.NET 11 Versão Prévia 7
Comportamento anterior
Anteriormente, o .NET não garantia o resultado de uma conversão não verificada de ponto flutuante para um tipo integral quando o valor excedia o limite do tipo de destino ou era NaN. Os resultados podem ser diferentes entre implementações de runtime, como CoreCLR e Mono, entre arquiteturas, como x86, x64, Arm32, Arm64 e WebAssembly, e entre conjuntos de instruções de hardware dentro de uma arquitetura, como x87, SSE2, AVX e AVX-512.
A alteração incompatível do .NET 9 destacou especificamente x86 e x64, em que as conversões geralmente retornavam valores sentinela em caso de estouro. O Arm64 já utilizava conversões com saturação por convenção. A alteração padronizava as conversões para os tipos inteiros mais amplos em vez de estabelecer os resultados do sentinela antigo como um contrato.
Para tipos integrais pequenos, uma sequência de conversão comum do CoreCLR no .NET 9 e no .NET 10 era uma conversão com saturação para int, seguida pelo estreitamento para o tipo de destino mediante o descarte dos bits mais altos. Essa sequência explica o comportamento que muitos aplicativos apresentaram, mas não constituía uma garantia para uma conversão direta de ponto flutuante em um tipo integral pequeno.
A tabela a seguir mostra os resultados dessa sequência de duas etapas para um valor de runtime float ou doublex. Essas entradas cabem em int, portanto, os exemplos isolam o efeito de descartar todos os bits, exceto os 8 ou 16 bits menos significativos do destino. Os bits retidos são interpretados como assinados para sbyte e short, e sem sinal para byte, ushort e char. Os resultados de char são mostrados numericamente.
| Converter para | Valor de x |
Bits menos significativos retidos | Exemplo de resultado anterior |
|---|---|---|---|
sbyte ou byte |
298 | 0x2A |
42 |
sbyte |
-298 | 0xD6 |
-42 |
byte |
-42 | 0xD6 |
214 |
short, ushort ou char |
65578 | 0x002A |
42 |
short |
-65578 | 0xFFD6 |
-42 |
ushort ou char |
-42 | 0xFFD6 |
65494 |
Por exemplo, o código a seguir pode retornar 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
O intermediário int é 65578 (0x0001002A). Com apenas seus 16 bits baixos retidos, o resultado é 42 (0x002A), em vez de saturação para short.MaxValue.
Novo comportamento
A partir do .NET 11, as conversões sem verificação passam a saturar nos limites do tipo de destino. Os valores finitos dentro do intervalo de destino continuam a ser arredondados para zero.
NaN converte em zero.
| Converter para | Abaixo do mínimo, incluindo infinito negativo | Acima do máximo, incluindo 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 |
O exemplo anterior agora retorna 32767 (short.MaxValue) em vez de 42. Da mesma forma, uma conversão de 255 para 42 agora retorna -42 em vez de ushort, e uma conversão de 0 para 65494 agora retorna byte em vez de 298.
Como elas são convertidas por meio de float, as conversões não verificadas correspondentes de Half também usam o novo comportamento.
ConvertToInteger<TInteger>(Single) e ConvertToInteger<TInteger>(Double) agora saturam corretamente para esses tipos de destino menores.
As conversões verificadas permanecem inalteradas e continuam lançando OverflowException quando ocorre estouro na conversão. Essa alteração não modifica as conversões de redução de inteiro para inteiro nem as conversões mais amplas de inteiro e de vetor abordadas pela alteração do .NET 9.
Tipo de mudança disruptiva
Esta é uma alteração comportamental.
Motivo da alteração
A alteração do .NET 9 estabeleceu o comportamento de saturação para conversões para tipos inteiros mais amplos, mas as conversões para destinos de 8 e 16 bits ainda apresentavam comportamento dependente do hardware e da implementação para valores fora do intervalo e NaN. Essa alteração torna essas conversões determinísticas e com comportamento de saturação, além de garantir que o JIT, o interpretador do CoreCLR e os valores pré-inicializados do Native AOT apresentem o mesmo resultado.
Ação recomendada
Se o seu código se baseia em resultados anteriores para entradas fora do intervalo, atualize-o para passar a esperar saturação nos limites do tipo de destino, sempre que possível.
Se você precisar do comportamento nativo da plataforma comumente usado antes dessas alterações, a solução alternativa mais simples será ConvertToIntegerNative<TInteger>(Single) ou ConvertToIntegerNative<TInteger>(Double). Por exemplo, substitua uma conversão direta (ushort)x por double.ConvertToIntegerNative<ushort>(x) quando x for um double, ou float.ConvertToIntegerNative<ushort>(x) quando for um float.
Você também pode selecionar a conversão intermediária explicitamente. Os exemplos a seguir usam uma double entrada x e um ushort destino:
| Comportamento necessário | Conversion |
|---|---|
| Conversão nativa de plataforma para o tipo de destino, que normalmente recupera o comportamento anterior | double.ConvertToIntegerNative<ushort>(x) |
Saturação para int, depois, estreitamento, correspondendo à sequência comum do CoreCLR no .NET 9 e no .NET 10 |
unchecked((ushort)(int)x) |
Conversão nativa da plataforma para int, seguida de redução, correspondendo a uma sequência comum anterior ao .NET 9 |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Use float.ConvertToIntegerNative para float entradas e substitua o tipo de destino apropriado para sbyte, byte, shortou char.
Assim como acontece com a alteração de .NET 9, ConvertToIntegerNativenão há garantia de reproduzir resultados anteriores para valores fora do intervalo ou NaN. Ele seleciona o comportamento que é eficiente para a plataforma atual, que pode ser alterado em runtimes, arquiteturas ou revisões de hardware. O uso explícito de (ushort)(int)x, em vez disso, seleciona uma conversão com saturação para int, seguida de um estreitamento para inteiro; isso não restaura o comportamento de todas as implementações históricas.
Se o valor convertido for usado como um índice de matriz, deslocamento de buffer ou comprimento, valide se o valor resultante está dentro dos limites necessários. Uma conversão que produz um valor utilizável em um computador não estabelece que a conversão nativa da plataforma o fará em outro.
APIs afetadas
- Conversões explícitas não verificadas de Single ou Double para SByte, Byte, Int16, UInt16 ou Char.
- Half Operadores de conversão explícita desmarcados:
-
ConvertToInteger<TInteger>(Single) quando
TIntegeré SByte, Byte, Int16, , UInt16ou Char. -
ConvertToInteger<TInteger>(Double) quando
TIntegeré SByte, Byte, Int16, , UInt16ou Char.