Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
No .NET 11, as conversões de vírgula flutuante sem verificação para sbyte, byte, short, ushort e char passam agora a ter um comportamento de saturação nos limites do tipo de destino. Valores demasiado pequenos ou demasiado grandes são definidos para o valor mínimo ou máximo do tipo de destino, respetivamente.
Esta alteração continua a alteração do .NET 9 para conversões de ponto flutuante para inteiro, que padronizavam as conversões de float e double para int, uint, long, e ulong. .NET 11 estende a saturação a destinos de 8 e 16 bits.
A alteração aplica-se ao CoreCLR, incluindo o seu interpretador, e ao AOT nativo. O Mono não está incluído nesta alteração.
Para mais informações, veja dotnet/runtime#128604.
Versão introduzida
.NET 11 Preview 7
Comportamento anterior
Anteriormente, o .NET não garantia o resultado de uma conversão não controlada de ponto flutuante para integral quando o valor ultrapassava o tipo de destino ou era NaN. Os resultados podem variar entre implementações em tempo de execução, 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 interruptiva do .NET 9 destacou em particular o x86 e o x64, em que as conversões devolviam normalmente valores sentinela em caso de overflow. O Arm64 já usava conversões saturantes por convenção. A alteração padronizou as conversões para os tipos inteiros mais amplos, em vez de estabelecer os antigos resultados sentinela como um contrato.
Para tipos inteiros pequenos, uma sequência comum de conversão do CoreCLR no .NET 9 e no .NET 10 era uma conversão com saturação para int, seguida de uma redução para o tipo de destino, descartando os bits de ordem superior. Esta sequência explica o comportamento que muitas aplicações tiveram, mas não constituía uma garantia para uma conversão direta de vírgula flutuante para um tipo integral pequeno.
A tabela seguinte mostra os resultados dessa sequência de dois passos para um tempo float de execução ou double valor x. Estas entradas encaixam-se em int, pelo que os exemplos isolam o efeito de descartar todos os bits do destino, exceto os baixos 8 ou 16. Os bits conservados são interpretados como com sinal para sbyte e short, e sem sinal para byte, ushort e char. Os resultados para 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 seguinte código poderia devolver 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
O intermédio int é 65578 (0x0001002A). Mantendo apenas os seus baixos 16 bits, o resultado é 42 (0x002A), em vez de saturação para short.MaxValue.
Novo comportamento
A partir do .NET 11, as conversões não verificadas 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-se em zero.
| Converter para | Abaixo do mínimo, incluindo infinito negativo | Acima do máximo, incluindo o 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 devolve 32767 (short.MaxValue) em vez de 42. De forma semelhante, uma conversão de 298 para byte agora retorna 255 em vez de 42, e uma conversão de -42 para ushort agora retorna 0 em vez de 65494.
Como convertem através 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 estes tipos de destino de menor dimensão.
As conversões verificadas mantêm-se inalteradas e continuam a lançar OverflowException quando ocorre overflow na conversão. Esta alteração não afeta as conversões redutoras de inteiro para inteiro nem as conversões alargadas de inteiros e vetores abrangidas pela alteração introduzida no .NET 9.
Tipo de mudança disruptiva
Esta mudança é uma mudança comportamental.
Motivo da mudança
A alteração introduzida no .NET 9 estabeleceu um comportamento de saturação para conversões para tipos inteiros de maior largura, mas as conversões para tipos de destino de 8 e 16 bits continuavam a ter um comportamento dependente do hardware e da implementação para valores fora do intervalo e NaN. Esta alteração confere a essas conversões um comportamento determinístico com saturação e faz com que os valores pré-inicializados do JIT, do interpretador CoreCLR e do Native AOT coincidam.
Ação recomendada
Se o seu código depende de resultados anteriores para entradas fora de alcance, atualize-o para esperar saturação nos limites do tipo de destino sempre que possível.
Se precisar do comportamento nativo da plataforma normalmente usado antes destas alterações, a solução mais simples é ConvertToIntegerNative<TInteger>(Single) ou ConvertToIntegerNative<TInteger>(Double). Por exemplo, substitua um cast direto (ushort)x por double.ConvertToIntegerNative<ushort>(x) quando x é um double, ou float.ConvertToIntegerNative<ushort>(x) quando é um float.
Também pode selecionar explicitamente a conversão intermédia. Os seguintes exemplos usam uma double entrada x e um ushort destino:
| Comportamento exigido | Conversion |
|---|---|
| Conversão nativa da plataforma para o tipo de destino, que normalmente recupera comportamentos anteriores | double.ConvertToIntegerNative<ushort>(x) |
Saturação até int, seguida de 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 restrição, seguindo 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 por sbyte, byte, short, ou char.
Tal como na alteração do .NET 9, ConvertToIntegerNativenão é garantido que reproduzam resultados anteriores para valores fora de intervalo ou NaN. Seleciona comportamentos eficientes para a plataforma atual, que podem mudar entre runtimes, arquiteturas ou revisões de hardware. Um explícito (ushort)(int)x seleciona em vez disso uma conversão saturante para int seguida de estreitamento inteiro; não restaura o comportamento de todas as implementações históricas.
Se o valor convertido for usado como índice de matriz, deslocamento do buffer ou comprimento, valide se o valor resultante se encontra dentro dos limites exigidos. Uma conversão que produz um valor utilizável numa máquina não estabelece que a conversão nativa da plataforma o faça noutra.
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 não verificados:
-
ConvertToInteger<TInteger>(Single) quando
TIntegeré SByte, Byte, Int16, UInt16, ou Char. -
ConvertToInteger<TInteger>(Double) quando
TIntegeré SByte, Byte, Int16, UInt16, ou Char.