Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans .NET 11, les conversions à virgule flottante non vérifiées vers sbyte, byte, short, ushort et char ont désormais un comportement saturant aux bornes du type de destination. Les valeurs trop petites ou trop grandes sont définies sur la valeur minimale ou maximale du type de destination, respectivement.
Cette modification s’inscrit dans la continuité de la modification apportée dans .NET 9 aux conversions de nombres à virgule flottante en entiers, laquelle a normalisé les conversions de float et double vers int, uint, long et ulong. .NET 11 étend la saturation aux destinations 8 et 16 bits.
La modification s’applique à CoreCLR, y compris son interpréteur et son AOT natif. Mono n’est pas inclus dans cette modification.
Pour plus d’informations, consultez dotnet/runtime#128604.
Version introduite
.NET 11 Preview 7
Comportement antérieur
Auparavant, .NET ne garantissait pas le résultat d’une conversion à virgule flottante non vérifiée en conversion intégrale lorsque la valeur débordait le type de destination ou était NaN. Les résultats peuvent différer entre les implémentations d’exécution, telles que CoreCLR et Mono, entre les architectures, telles que x86, x64, Arm32, Arm64 et WebAssembly, et entre les ensembles d’instructions matérielles au sein d’une architecture, telles que x87, SSE2, AVX et AVX-512.
Le changement avec rupture de compatibilité de .NET 9 mettait spécifiquement en avant x86 et x64, où les conversions retournaient souvent des valeurs sentinelles en cas de dépassement de capacité. Arm64 utilise déjà des conversions saturantes par convention. Cette modification a normalisé les conversions vers des types entiers plus larges, au lieu d’ériger les anciens résultats sentinelles en tant que contrat.
Pour les petits types intégraux, une séquence de conversion CoreCLR courante dans .NET 9 et .NET 10 était une conversion saturante en int, suivie d’une réduction du type de destination en ignorant les bits élevés. Cette séquence explique le comportement qu’ont connu de nombreuses applications, mais elle ne constituait pas un contrat garanti pour une conversion directe d’un type à virgule flottante vers un type entier de petite taille.
Le tableau suivant montre les résultats de cette séquence en deux étapes pour une valeur de runtime float ou double de x. Ces entrées tiennent dans int, de sorte que les exemples isolent l’effet de la suppression de tous les bits, sauf les 8 ou 16 bits de poids faible de la destination. Les bits conservés sont interprétés comme signés pour sbyte et short, et non signés pour byte, ushortet char. Les résultats pour char sont affichés numériquement.
| Convertir en | Valeur de x |
Bits faibles conservés | Exemple de résultat précédent |
|---|---|---|---|
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 |
Par exemple, le code suivant peut retourner 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
L’intermédiaire int est 65578 (0x0001002A). Avec seulement ses 16 bits de poids faible conservés, le résultat est 42 (0x002A), au lieu d'une saturation à short.MaxValue.
Nouveau comportement
À compter de .NET 11, les conversions non vérifiées sont saturées aux limites du type de destination. Les valeurs finies de la plage de destination continuent d’être arrondies vers zéro.
NaN convertit en zéro.
| Convertir en | Au-dessous du minimum, y compris l’infini négatif | Au-dessus du maximum, y compris l’infini positif | 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’exemple précédent retourne 32767 maintenant (short.MaxValue) au lieu de 42. De même, une conversion de 298 vers maintenant byte retourne 255 au lieu de 42, et une conversion de -42 vers maintenant ushort retourne 0 au lieu de 65494.
Étant donné qu’ils convertissent par le biais float, les conversions Half non vérifiées correspondantes utilisent également le nouveau comportement.
ConvertToInteger<TInteger>(Single) et ConvertToInteger<TInteger>(Double) se saturent désormais correctement pour ces petits types de destination.
Les conversions vérifiées restent inchangées et continuent à lever l’exception OverflowException lorsque la conversion provoque un débordement. Cette modification n’affecte pas les conversions restrictives d’entier à entier ni les conversions plus étendues d’entiers et de vecteurs couvertes par la modification apportée à .NET 9.
Type de changement avec rupture de compatibilité
Ce changement est un changement de comportement.
Raison du changement
La modification de .NET 9 a établi un comportement de saturation pour les conversions vers des types entiers plus larges, mais les conversions vers des types de destination sur 8 et 16 bits présentaient toujours un comportement dépendant du matériel et de l’implémentation pour les valeurs hors plage et NaN. Cette modification confère à ces conversions un comportement déterministe avec saturation et fait concorder le JIT, l’interpréteur CoreCLR et les valeurs préinitialisées de Native AOT.
Action recommandée
Si votre code s’appuie sur les résultats précédents pour les entrées hors plage, mettez-le à jour pour s’attendre à une saturation dans les limites du type de destination, le cas échéant.
Si vous avez besoin du comportement natif de la plateforme couramment utilisé avant ces modifications, la solution de contournement la plus simple est ConvertToIntegerNative<TInteger>(Single) ou ConvertToIntegerNative<TInteger>(Double). Par exemple, remplacez une conversion de type directe (ushort)x par x lorsque double est un float.ConvertToIntegerNative<ushort>(x), ou par float lorsque c’est un double.ConvertToIntegerNative<ushort>(x).
Vous pouvez également sélectionner explicitement la conversion intermédiaire. Les exemples suivants utilisent une double entrée x et une ushort destination :
| Comportement requis | Conversion |
|---|---|
| Conversion native de la plateforme vers le type de destination, qui récupère généralement le comportement antérieur | double.ConvertToIntegerNative<ushort>(x) |
Saturation à int, puis réduction, conforme à la séquence commune de CoreCLR dans .NET 9 et .NET 10 |
unchecked((ushort)(int)x) |
Conversion native de la plateforme vers int, puis rétrécissement, correspondant à une séquence pré-.NET 9 commune |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Utilisez float.ConvertToIntegerNative pour les entrées float et remplacez par le type de destination approprié sbyte, byte, short ou char.
Comme avec la modification .NET 9, ConvertToIntegerNativeil n’est pas garanti de reproduire les résultats précédents pour les valeurs hors plage ou NaN. Il sélectionne le comportement efficace pour la plateforme actuelle, qui peut changer entre les runtimes, les architectures ou les révisions matérielles. Une conversion explicite (ushort)(int)x sélectionne plutôt une conversion saturante en int suivi d’un rétrécissement entier ; elle ne restaure pas le comportement de chaque implémentation historique.
Si la valeur convertie est utilisée comme index de tableau, décalage de mémoire tampon ou longueur, vérifiez que la valeur résultante se trouve dans les limites requises. Une conversion qui produit une valeur utilisable sur une machine n’établit pas que la conversion native de la plateforme le fera sur une autre.
API affectées
- Conversions explicites non vérifiées de Single ou Double vers SByte, Byte, Int16, UInt16 ou Char.
- Half Opérateurs de conversion explicite désactivés :
-
ConvertToInteger<TInteger>(Single) quand
TIntegerest SByte, , Byte, Int16, UInt16ou Char. -
ConvertToInteger<TInteger>(Double) quand
TIntegerest SByte, , Byte, Int16, UInt16ou Char.