Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I .NET 11 har okontrollerade flyttalskonverteringar till sbyte, byte, short, ushort och char nu mättnadsbeteende vid gränserna för måltypen. Värden som är för små eller för stora anges till måltypens lägsta respektive högsta värde.
Den här ändringen bygger vidare på .NET 9-ändringen för konverteringar från flyttal till heltal, som standardiserade konverteringarna från float och double till int, uint, long och ulong. .NET 11 utökar saturering till 8- och 16-bitarsmåltyper.
Ändringen gäller för CoreCLR, inklusive dess tolk och intern AOT. Mono ingår inte i den här ändringen.
Mer information finns i dotnet/runtime#128604.
Version som introducerades
.NET 11 Förhandsversion 7
Tidigare beteende
Tidigare garanterade .NET inte resultatet av en okontrollerad konvertering från flyttal till heltal när värdet överskred måltypens värdeintervall eller var NaN. Resultaten kan skilja sig mellan körningsimplementeringar, till exempel CoreCLR och Mono, mellan arkitekturer som x86, x64, Arm32, Arm64 och WebAssembly och mellan maskinvaruinstruktionsuppsättningar i en arkitektur, till exempel x87, SSE2, AVX och AVX-512.
Den kompatibilitetsbrytande ändringen i .NET 9 lyfte särskilt fram x86 och x64, där konverteringar ofta returnerade sentinel-värden vid överflöde. Arm64 använde redan mättade konverteringar efter konvention. Ändringen standardiserade konverteringar till de bredare heltalstyperna i stället för att etablera de gamla sentinel-resultaten som ett kontrakt.
För små integraltyper var en vanlig CoreCLR-konverteringssekvens i .NET 9 och .NET 10 en mättande konvertering till int, följt av att begränsa till måltypen genom att ta bort de höga bitarna. Den här sekvensen förklarar det beteende som många applikationer uppvisade, men det var inte ett garanterat åtagande för en direkt typomvandling från flyttal till små heltalstyper.
Följande tabell visar resultat från den tvåstegssekvensen för en körning float eller double ett värde x. Dessa indatavärden får plats i int, så exemplen visar isolerat effekten av att kassera alla utom destinationens lägsta 8 eller 16 bitar. De kvarhållna bitarna tolkas som signerade för sbyte och short, och osignerade för byte, ushortoch char. Resultat för char visas numeriskt.
| Konvertera till | Värdet för x |
Kvarhållna låga bitar | Exempel på föregående resultat |
|---|---|---|---|
sbyte eller byte |
298 | 0x2A |
42 |
sbyte |
-298 | 0xD6 |
-42 |
byte |
-42 | 0xD6 |
214 |
short, ushort eller char |
65578 | 0x002A |
42 |
short |
-65578 | 0xFFD6 |
-42 |
ushort eller char |
-42 | 0xFFD6 |
65494 |
Följande kod kan till exempel returnera 42:
static short ConvertValue(double value)
{
return unchecked((short)value);
}
short result = ConvertValue(65578.0);
Mellanliggande int är 65578 (0x0001002A). Om endast de lägre 16 bitarna behålls blir resultatet 42 (0x002A), i stället för att mättas till short.MaxValue.
Nytt beteende
Från och med .NET 11 begränsas okontrollerade konverteringar till måltypens gränsvärden. Finita värden inom målintervallet fortsätter att avrundas mot noll.
NaN konverteras till noll.
| Konvertera till | Under minimum, inklusive negativ oändlighet | Över maximalt, inklusive positiv oändlighet | 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 |
Föregående exempel returnerar 32767 nu (short.MaxValue) i stället för 42. På samma sätt returnerar nu en konvertering från 255 till 298byte i stället för 42, och en konvertering från -42 till ushort returnerar nu 0 i stället för 65494.
Eftersom de konverterar via float, använder motsvarande okontrollerade konverteringar från Half också det nya beteendet.
ConvertToInteger<TInteger>(Single) och ConvertToInteger<TInteger>(Double) mättas nu korrekt för dessa små måltyper.
Kontrollerade konverteringar är oförändrade och fortsätter att generera OverflowException vid spill i konverteringen. Den här ändringen ändrar inte heltals-till-heltalskonverteringar eller bredare heltals- och vektorkonverteringar som omfattas av ändringen .NET 9.
Typ av kompatibilitetsbrytande ändring
Den här ändringen är en beteendeförändring.
Orsak till ändringen
.NET 9-ändringen införde mättande beteende för konverteringar till större heltalstyper, men konverteringar till 8-bitars- och 16-bitarsmåltyper hade fortfarande maskinvaru- och implementeringsberoende beteende för värden utanför intervallet och NaN. Den här ändringen ger dessa konverteringar ett deterministiskt, mättande beteende och gör att JIT, CoreCLR-tolken och de förinitierade värdena i Native AOT stämmer överens.
Rekommenderad åtgärd
Om koden förlitar sig på tidigare resultat för indata utanför intervallet uppdaterar du den så att den förväntar sig mättnad vid måltypens gränser där det är möjligt.
Om du behöver det plattformsbaserade beteende som ofta används före dessa ändringar är ConvertToIntegerNative<TInteger>(Single) den enklaste lösningen eller ConvertToIntegerNative<TInteger>(Double). Ersätt till exempel en direkt typkonvertering double med float.ConvertToIntegerNative<ushort>(x) när float är en x, eller double.ConvertToIntegerNative<ushort>(x) när den är en (ushort)x.
Du kan också välja den mellanliggande konverteringen explicit. I följande exempel används indata doublex och mål ushort :
| Obligatoriskt beteende | Conversion |
|---|---|
| Plattformsbaserad konvertering till måltypen, som ofta återställer tidigare beteende | double.ConvertToIntegerNative<ushort>(x) |
Mättnad till int, sedan begränsa, matcha den gemensamma .NET 9 och .NET 10 CoreCLR-sekvens |
unchecked((ushort)(int)x) |
Plattformsintern konvertering till int, följt av insnävning, vilket motsvarar en vanlig sekvens före .NET 9 |
unchecked((ushort)double.ConvertToIntegerNative<int>(x)) |
Använd float.ConvertToIntegerNative för float indata och ersätt lämplig måltyp för sbyte, byte, shorteller char.
Precis som med .NET 9-ändringen ConvertToIntegerNativegaranteras inte att tidigare resultat återskapas för värden som ligger utom intervallet eller NaN. Den väljer beteende som är effektivt för den aktuella plattformen, vilket kan ändras mellan körningar, arkitekturer eller maskinvarurevisioner. Ett explicit (ushort)(int)x väljer i stället en mättande konvertering till int, följd av heltalsminskning; det återställer inte beteendet hos alla historiska implementeringar.
Om det konverterade värdet används som ett matrisindex, buffertförskjutning eller längd kontrollerar du att det resulterande värdet ligger inom de obligatoriska gränserna. En konvertering som skapar ett användbart värde på en dator fastställer inte att plattformsbaserad konvertering kommer att göra det på en annan.
Berörda API:er
- Okontrollerade explicita typomvandlingar från Single eller Double till SByte, Byte, Int16, UInt16 eller Char.
- Half explicita konverteringsoperatorer som inte är markerade:
-
ConvertToInteger<TInteger>(Single) när
TIntegerär SByte, Byte, Int16, UInt16eller Char. -
ConvertToInteger<TInteger>(Double) när
TIntegerär SByte, Byte, Int16, UInt16eller Char.