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.
Le conversioni tra Decimal e i tipi binari a virgola mobile, e le conversioni da BigInteger ai tipi binari a virgola mobile, ora producono risultati correttamente arrotondati. In precedenza, queste conversioni potevano troncare, arrotondare tramite valori intermedi o eliminare cifre significative.
Versione introdotta
.NET 11 Preview 7
Comportamento precedente
Le conversioni da float e double in decimal conservano solo 7 e 15 cifre decimali significative, rispettivamente. Ciò potrebbe nascondere la differenza tra un valore letterale a virgola mobile binaria e un valore letterale decimale:
using System.Globalization;
double value = 1.23;
decimal converted = (decimal)value;
Console.WriteLine(converted.ToString("G29", CultureInfo.InvariantCulture));
L'output è stato:
1.23
Le conversioni da decimal a float o double potrebbero essere arrotondate più di una volta. Per esempio:
using System.Globalization;
decimal value = 10000000000000.099609375m;
double converted = (double)value;
Console.WriteLine(converted.ToString("G99", CultureInfo.InvariantCulture));
L'output è stato:
10000000000000.09765625
Le conversioni da decimal a Half o BFloat16 convertivano prima il valore in float, il che poteva anche produrre un risultato arrotondato in modo errato.
Nelle conversioni da BigInteger a double, i bit scartati venivano troncati invece di essere arrotondati al valore rappresentabile più vicino. Per esempio:
using System.Globalization;
using System.Numerics;
BigInteger value = long.MaxValue / 2;
double converted = (double)value;
Console.WriteLine(converted.ToString("G17", CultureInfo.InvariantCulture));
L'output è stato:
4.6116860184273874E+18
Le conversioni da BigInteger a float, Halfo BFloat16 hanno prima convertito il valore in double, che potrebbe arrotondare due volte e produrre un risultato un'unità nell'ultima posizione lontano dal valore rappresentabile più vicino.
Nuovo comportamento
A partire da .NET 11, le conversioni arrotondano il valore di origine esatto una volta al valore di destinazione rappresentabile più vicino.
Per il primo esempio, il double valore non è esattamente 1.23. Il valore esatto è approssimativamente 1.229999999999999982236431605997..., quindi il valore convertito decimal è ora:
1.229999999999999982236431606
Per il secondo esempio, il valore convertito double è ora:
10000000000000.099609375
Le conversioni da decimal a Half o BFloat16 vengono arrotondate correttamente e possono produrre un modello di bit di destinazione diverso rispetto alle versioni precedenti.
Per l'esempio BigInteger , il valore convertito double è ora:
4.6116860184273879E+18
Anche le conversioni da BigInteger a float, Halfo BFloat16 vengono arrotondate correttamente.
Se una conversione viene valutata come costante in fase di compilazione, un compilatore ospitato dall'SDK .NET 11 Preview 7 o un SDK successivo può incorporare il nuovo risultato nell'assembly di output quando il progetto viene ricompilato, indipendentemente dal framework di destinazione del progetto.
Tipo di cambiamento che interrompe la compatibilità
Questa modifica è una modifica funzionale.
Motivo della modifica
Gli algoritmi di conversione precedenti perdono informazioni che il tipo di destinazione potrebbe rappresentare e a volte hanno selezionato un valore diverso dal risultato rappresentabile più vicino. Ciò ha causato errori di precisione in entrambe le direzioni di conversione di decimal e nelle conversioni da BigInteger. I nuovi algoritmi seguono la regola a virgola mobile prevista del calcolo della conversione come se con precisione intermedia esatta, seguita da un singolo arrotondamento al tipo di destinazione.
Per altre informazioni, vedere dotnet/runtime#130565 e dotnet/runtime#130566.
Azione consigliata
Non presupporre che un valore letterale a virgola mobile binaria e un valore letterale decimale con lo stesso testo di origine rappresentino lo stesso valore. Se un valore è destinato a essere decimale, utilizzare un letterale decimale:
decimal value = 123.4567m;
anziché una conversione da un valore double letterale:
decimal value = (decimal)123.4567;
Per ripristinare il risultato precedente in generale quando si esegue la conversione in decimal, arrotondare il valore convertito in 7 cifre decimali significative per un'origine float o 15 cifre decimali significative per un'origine double . La conversione precedente ha arrotondato al valore più vicino e, in caso di parità, al valore pari; non ha effettuato un troncamento. I valori positivi e negativi sono stati gestiti simmetricamente.
Ad esempio, la formattazione del valore di origine con la stringa di formato numerico standard G7 o G15 e l'analisi del risultato consentono di eseguire il corrispondente arrotondamento alle cifre significative per valori finiti arbitrari:
using System.Globalization;
static decimal ConvertToDecimalLikePrevious(float value)
{
Span<char> text = stackalloc char[32];
value.TryFormat(text, out int length, "G7", CultureInfo.InvariantCulture);
return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture);
}
static decimal ConvertToDecimalLikePrevious(double value)
{
Span<char> text = stackalloc char[32];
value.TryFormat(text, out int length, "G15", CultureInfo.InvariantCulture);
return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture);
}
decimal fromFloat = ConvertToDecimalLikePrevious(14.1f); // 14.1
decimal fromDouble = ConvertToDecimalLikePrevious(-123.4567); // -123.4567
L'implementazione basata su span non alloca. I valori al di fuori dell'intervallo di decimal continuano a generare un'eccezione durante l'analisi, come durante la conversione.
Aggiornare i test e i valori previsti serializzati che codificavano il risultato precedente, arrotondato in modo non corretto. Ciò include il codice che dipendeva dal troncamento della conversione verso zero BigInteger. Se un'applicazione richiede uno specifico modello di bit legacy float, doubleHalf, o BFloat16 per un protocollo o un formato di file, codificare il modello di bit in modo esplicito anziché riprodurlo tramite una conversione numerica.
Non esiste alcuna opzione di compatibilità per ripristinare gli algoritmi di conversione precedenti.
Le API interessate
- Decimal.Decimal(Single)
- Decimal.Decimal(Double)
- Conversioni esplicite da Single e Double a Decimal
- Conversioni esplicite da Decimal a Single e Double
- Decimal.ToSingle(Decimal)
- Decimal.ToDouble(Decimal)
- Convert.ToDecimal(Single)
- Convert.ToDecimal(Double)
- Convert.ToSingle(Decimal)
- Convert.ToDouble(Decimal)
- Decimal.CreateChecked<TOther>(TOther), Decimal.CreateSaturating<TOther>(TOther) e Decimal.CreateTruncating<TOther>(TOther) quando vengono convertiti in o da Single o Double
- Conversione esplicita da Decimal a Half
- Conversione esplicita da Decimal a BFloat16
- Conversioni esplicite da BigInteger a Double, Single, Halfe BFloat16
- Conversioni equivalenti eseguite tramite IConvertible o le interfacce matematiche generiche