Oharra
Baimena behar duzu orria atzitzeko. Direktorioetan saioa has dezakezu edo haiek alda ditzakezu.
Baimena behar duzu orria atzitzeko. Direktorioak alda ditzakezu.
Las conversiones entre Decimal y los tipos de punto flotante binario, y las conversiones de BigInteger a tipos de punto flotante binario, ahora producen resultados correctamente redondeados. Anteriormente, estas conversiones podían truncarse, redondear a través de valores intermedios o descartar dígitos significativos.
Versión introducida
.NET 11 Preview 7
Comportamiento anterior
Las conversiones de float y double a decimal retuvieron solo 7 y 15 cifras decimales significativas, respectivamente. Esto podría ocultar la diferencia entre un literal de punto flotante binario y un literal decimal:
using System.Globalization;
double value = 1.23;
decimal converted = (decimal)value;
Console.WriteLine(converted.ToString("G29", CultureInfo.InvariantCulture));
La salida era:
1.23
Las conversiones de float a double o decimal podrían redondearse más de una vez. Por ejemplo:
using System.Globalization;
decimal value = 10000000000000.099609375m;
double converted = (double)value;
Console.WriteLine(converted.ToString("G99", CultureInfo.InvariantCulture));
La salida era:
10000000000000.09765625
Las conversiones de decimal a Half o BFloat16 primero convirtieron el valor en float, lo que también podría producir un resultado redondeado incorrectamente.
Las conversiones de BigInteger a double truncaban los bits descartados en lugar de redondear al valor representable más cercano. Por ejemplo:
using System.Globalization;
using System.Numerics;
BigInteger value = long.MaxValue / 2;
double converted = (double)value;
Console.WriteLine(converted.ToString("G17", CultureInfo.InvariantCulture));
La salida era:
4.6116860184273874E+18
Las conversiones de BigInteger a float, Halfo BFloat16 convierten primero el valor en double, que podría redondear dos veces y generar un resultado de una unidad en el último lugar lejos del valor representable más cercano.
Nuevo comportamiento
A partir de .NET 11, las conversiones redondean el valor de origen exacto una vez al valor de destino que se puede representar más cercano.
En el primer ejemplo, el double valor no es exactamente 1.23. Su valor exacto es aproximadamente 1.229999999999999982236431605997..., por lo que el valor convertido decimal es ahora:
1.229999999999999982236431606
En el segundo ejemplo, el valor convertido double es ahora:
10000000000000.099609375
Las conversiones de decimal a o HalfBFloat16 también se redondean correctamente y pueden producir un patrón de bits de destino diferente al de las versiones anteriores.
En el BigInteger ejemplo, el valor convertido double es ahora:
4.6116860184273879E+18
Las conversiones de BigInteger a float, Halfo BFloat16 también se redondean correctamente.
Si una conversión se evalúa como una constante en tiempo de compilación, un compilador hospedado por el SDK de la versión preliminar 7 de .NET 11 o un SDK posterior puede insertar el nuevo resultado en el ensamblado de salida cuando se vuelve a generar el proyecto, independientemente del marco de destino del proyecto.
Tipo de cambio disruptivo
Este es un cambio de comportamiento.
Motivo del cambio
Los algoritmos de conversión anteriores perdieron información que el tipo de destino podría representar y, a veces, seleccionaron un valor distinto del resultado representable más cercano. Esto provocó errores de precisión en ambas direcciones de la conversión de decimal y en las conversiones desde BigInteger. Los nuevos algoritmos siguen la regla de punto flotante esperada de cálculo de la conversión como si tuviera una precisión intermedia exacta, seguida de un único redondeo al tipo de destino.
Para obtener más información, vea dotnet/runtime#130565 y dotnet/runtime#130566.
Acción recomendada
No suponga que un literal de punto flotante binario y un literal decimal con el mismo texto de origen representan el mismo valor. Si un valor está pensado para ser decimal, use un literal decimal:
decimal value = 123.4567m;
en lugar de una conversión a partir de un literal double:
decimal value = (decimal)123.4567;
Para restaurar el resultado anterior en general cuando se convierte en decimal, redondea el valor convertido a 7 dígitos decimales significativos para un float origen o 15 dígitos decimales significativos para un double origen. La conversión anterior redondeó al valor más cercano y, en caso de empate, al par; no truncó. Los valores positivos y negativos se controlaron simétricamente.
Por ejemplo, dar formato al valor de origen con la G7 cadena de formato numérico estándar o G15 y analizar el resultado realiza el redondeo de dígitos significativos correspondiente para valores finitos arbitrarios:
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
La implementación basada en span no realiza asignaciones de memoria. Los valores fuera del intervalo de decimal continúan produciendo una excepción durante el análisis, como lo hicieron durante la conversión.
Actualice las pruebas y los valores esperados serializados que codificaron el resultado anterior redondeado incorrectamente. Esto incluye código que dependía del truncamiento hacia cero en la conversión BigInteger. Si una aplicación requiere un patrón de bits heredado floatespecífico , double, Halfo BFloat16 para un formato de archivo o protocolo, codifique ese patrón de bits explícitamente en lugar de reproducirlo a través de una conversión numérica.
No existe ninguna opción de compatibilidad para restaurar los algoritmos de conversión previos.
Las APIs afectadas
- Decimal.Decimal(Single)
- Decimal.Decimal(Double)
- Conversiones explícitas de Single y Double a Decimal
- Conversiones explícitas de Decimal a Single y 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)y Decimal.CreateTruncating<TOther>(TOther) cuando se convierten en o desde Single o Double
- Conversión explícita de Decimal a Half
- Conversión explícita de Decimal a BFloat16
- Conversiones explícitas de BigInteger a Double, Single, Halfy BFloat16
- Conversiones equivalentes realizadas a través de IConvertible o de las interfaces matemáticas genéricas