Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In .NET 11, le conversioni da virgola mobile non controllate in sbyte, byte, short, ushort e char ora hanno un comportamento di saturazione ai limiti del tipo di destinazione. I valori troppo piccoli o troppo grandi vengono impostati rispettivamente sul valore minimo o massimo del tipo di destinazione.
Questa modifica prosegue la modifica di .NET 9 relativa alle conversioni da virgola mobile a intero, che ha standardizzato le conversioni da float e double a int, long, uint e ulong. .NET 11 estende la saturazione alle destinazioni a 8 e 16 bit.
La modifica si applica a CoreCLR, incluso il relativo interprete e AOT nativo. Mono non è incluso in questa modifica.
Per altre informazioni, vedere dotnet/runtime#128604.
Versione introdotta
.NET 11 Preview 7
Comportamento precedente
In precedenza, .NET non garantiva il risultato di una conversione non verificata da virgola mobile a un tipo intero quando il valore eccedeva i limiti del tipo di destinazione o era NaN. I risultati possono variare tra le implementazioni di runtime, ad esempio CoreCLR e Mono, tra architetture, ad esempio x86, x64, Arm32, Arm64 e WebAssembly, e tra set di istruzioni hardware all'interno di un'architettura, ad esempio x87, SSE2, AVX e AVX-512.
La breaking change di .NET 9 metteva specificamente in evidenza x86 e x64, in cui le conversioni restituivano comunemente valori sentinella in caso di overflow. Arm64 usa già per convenzione conversioni con saturazione. La modifica ha standardizzato le conversioni verso i tipi interi più ampi anziché stabilire i precedenti valori sentinella come comportamento garantito.
Per i tipi integrali di piccole dimensioni, una comune sequenza di conversione di CoreCLR in .NET 9 e .NET 10 era una conversione con saturazione in int, seguita dal restringimento al tipo di destinazione mediante lo scarto dei bit più significativi. Questa sequenza spiega il comportamento riscontrato da molte applicazioni, ma non costituiva una garanzia per un cast diretto da virgola mobile a un tipo intero di piccole dimensioni.
La tabella seguente mostra i risultati di quella sequenza in due passaggi per un valore di runtime float o double pari a x. Questi input rientrano in int, quindi gli esempi isolano l'effetto dello scartare tutti i bit tranne gli 8 o 16 bit meno significativi della destinazione. I bit conservati vengono interpretati come firmati per sbyte e shorte senza segno per byte, ushorte char. I risultati per char vengono visualizzati numericamente.
| Converti in | Valore di x |
Bit bassi conservati | Esempio di risultato precedente |
|---|---|---|---|
sbyte oppure byte |
298 | 0x2A |
42 |
sbyte |
-298 | 0xD6 |
-42 |
byte |
-42 | 0xD6 |
214 |
short, ushort o char |
65578 | 0x002A |
42 |
short |
-65578 | 0xFFD6 |
-42 |
ushort oppure char |
-42 | 0xFFD6 |
65494 |
Ad esempio, il codice seguente potrebbe restituire 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
L'elemento intermedio int è 65578 (0x0001002A). Poiché vengono mantenuti solo i 16 bit meno significativi, il risultato è 42 (0x002A), anziché una saturazione a short.MaxValue.
Nuovo comportamento
A partire da .NET 11, le conversioni non controllate vengono saturate ai limiti del tipo di destinazione. I valori finiti all'interno dell'intervallo di destinazione continuano a essere arrotondati verso zero.
NaN viene convertito in zero.
| Converti in | Al di sotto del valore minimo, compreso l’infinito negativo | Al di sopra del valore massimo, incluso l'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 |
L'esempio precedente restituisce 32767 ora (short.MaxValue) anziché 42. Analogamente, una conversione da 298 a byte ora restituisce 255 invece di 42e una conversione da -42 a ushort ora restituisce 0 anziché 65494.
Poiché effettuano la conversione tramite float, anche le corrispondenti conversioni unchecked da Half utilizzano il nuovo comportamento.
ConvertToInteger<TInteger>(Single) e ConvertToInteger<TInteger>(Double) ora vanno correttamente in saturazione per questi tipi di destinazione di piccole dimensioni.
Le conversioni controllate restano invariate e continuano a sollevare OverflowException quando si verifica un overflow della conversione. Questa modifica non altera le conversioni con riduzione da intero a intero né le conversioni più estese di interi e vettori previste dalla modifica di .NET 9.
Tipo di modifica che causa un'interruzione
Questa modifica è una modifica funzionale.
Motivo della modifica
La modifica di .NET 9 ha introdotto il comportamento di saturazione per le conversioni verso tipi interi più ampi, ma le conversioni verso tipi di destinazione a 8 e 16 bit continuavano ad avere un comportamento dipendente dall'hardware e dall'implementazione per i valori fuori intervallo e NaN. Questa modifica conferisce a quelle conversioni un comportamento deterministico e con saturazione e fa sì che i valori preinizializzati di JIT, interprete CoreCLR e Native AOT coincidano.
Azione consigliata
Se il codice si basa sui risultati precedenti per input fuori intervallo, aggiornalo in modo che tenga conto, ove possibile, della saturazione ai limiti del tipo di destinazione.
Se è necessario il comportamento nativo della piattaforma comunemente usato prima di queste modifiche, la soluzione alternativa più semplice è ConvertToIntegerNative<TInteger>(Single) o ConvertToIntegerNative<TInteger>(Double). Ad esempio, sostituire un cast diretto (ushort)x con double.ConvertToIntegerNative<ushort>(x) quando x è un double oggetto o float.ConvertToIntegerNative<ushort>(x) quando è un float oggetto.
È anche possibile selezionare la conversione intermedia in modo esplicito. Gli esempi seguenti utilizzano un double input x e una ushort destinazione:
| Comportamento obbligatorio | Conversion |
|---|---|
| Conversione nativa della piattaforma nel tipo di destinazione, che in genere recupera il comportamento precedente | double.ConvertToIntegerNative<ushort>(x) |
Saturazione fino a int, poi restringimento, in linea con la comune sequenza CoreCLR di .NET 9 e .NET 10 |
unchecked((ushort)(int)x) |
Conversione nativa della piattaforma in int, quindi restringimento, in linea con una comune sequenza precedente a .NET 9 |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Usare float.ConvertToIntegerNative per float gli input e sostituire il tipo di destinazione appropriato per sbyte, byte, shorto char.
Come per la modifica di .NET 9, ConvertToIntegerNativenon garantisce la riproduzione dei risultati precedenti per valori fuori intervallo o NaN. Seleziona il comportamento più efficiente per la piattaforma corrente, che può variare tra ambienti di runtime, architetture o revisioni hardware. Un (ushort)(int)x esplicito seleziona invece una conversione con saturazione in int, seguita da un restringimento a intero; non ripristina il comportamento di tutte le implementazioni storiche.
Se il valore convertito viene usato come indice di matrice, offset del buffer o lunghezza, verificare che il valore risultante si trova entro i limiti necessari. Una conversione che produce un valore utilizzabile in un computer non stabilisce che la conversione nativa della piattaforma verrà eseguita su un'altra.
Le API interessate
- Conversioni esplicite di tipo non controllate da Single o Double a SByte, Byte, Int16, UInt16 o Char.
- Half Operatori di conversione espliciti deselezionati:
-
ConvertToInteger<TInteger>(Single)quando
TIntegerè SByte, ByteInt16, UInt16, o Char. -
ConvertToInteger<TInteger>(Double)quando
TIntegerè SByte, ByteInt16, UInt16, o Char.