¿Cuándo debo usar doble en lugar de decimal?

265

Puedo nombrar tres ventajas de usar double(o float) en lugar de decimal:

  1. Utiliza menos memoria.
  2. Más rápido porque las operaciones matemáticas de coma flotante son compatibles de forma nativa con los procesadores.
  3. Puede representar un mayor rango de números.

Pero estas ventajas parecen aplicarse solo a operaciones de cálculo intensivo, como las que se encuentran en el software de modelado. Por supuesto, los dobles no deben usarse cuando se requiere precisión, como los cálculos financieros. Entonces, ¿hay alguna razón práctica para elegir double(o float) en lugar de decimalen aplicaciones "normales"?

Editado para agregar: Gracias por todas las excelentes respuestas, aprendí de ellas.

Una pregunta más: algunas personas señalaron que los dobles pueden representar con mayor precisión los números reales. Cuando se declara, pensaría que generalmente también los representan con mayor precisión. Pero, ¿es una afirmación verdadera que la precisión puede disminuir (a veces significativamente) cuando se realizan operaciones de coma flotante?

Jamie Ide
fuente
vea también stackoverflow.com/questions/2545567/…
Ian Ringrose el
55
Esto se vota con bastante regularidad y todavía lucho con eso. Por ejemplo, estoy trabajando en una aplicación que hace cálculos financieros, así que estoy usando decimal en todo momento. Pero las funciones Math y VisualBasic.Financial usan el doble, por lo que hay muchas conversiones, lo que me hace cuestionar constantemente el uso del decimal.
Jamie Ide
@JamieIde es una locura, las funciones financieras usan el doble, el dinero siempre debe estar en decimal.
Chris Marisic
@ChrisMarisic Pero, ¿qué puede hacer Jamie Ide trabajando con basura heredada usando doble? Entonces deberías usar el doble también, de lo contrario, las muchas conversiones causarán errores de redondeo ... no es de extrañar que haya mencionado VisualBasic pfffhh .....
Elisabeth
@Elisabeth, probablemente usaría una biblioteca diferente que admitiera correctamente el decimal. Lo que VisualBasic.Financial proporciona probablemente exista en muchas otras bibliotecas hoy
Chris Marisic

Respuestas:

306

Creo que ha resumido las ventajas bastante bien. Sin embargo, te estás perdiendo un punto. El decimaltipo solo es más preciso para representar números de base 10 (por ejemplo, los utilizados en los cálculos de moneda / financieros). En general, el doubletipo ofrecerá al menos la misma precisión (alguien me corrige si me equivoco) y definitivamente una mayor velocidad para números reales arbitrarios. La conclusión simple es: al considerar cuál usar, siempre use a doublemenos que necesite la base 10precisión que decimalofrece.

Editar:

Con respecto a su pregunta adicional sobre la disminución en la precisión de los números de punto flotante después de las operaciones, este es un problema un poco más sutil. De hecho, la precisión (uso el término indistintamente para precisión aquí) disminuirá constantemente después de realizar cada operación. Esto se debe a dos razones:

  1. el hecho de que ciertos números (más obviamente decimales) no pueden representarse realmente en forma de coma flotante
  2. se producen errores de redondeo, como si estuviera haciendo el cálculo a mano. Sin embargo, depende en gran medida del contexto (cuántas operaciones está realizando) si estos errores son lo suficientemente significativos como para justificar mucho pensamiento.

En todos los casos, si desea comparar dos números de punto flotante que en teoría deberían ser equivalentes (pero se llegó a utilizar diferentes cálculos), debe permitir un cierto grado de tolerancia (cuánto varía, pero generalmente es muy pequeño) .

Para obtener una descripción más detallada de los casos particulares en los que se pueden introducir errores en las precisiones, consulte la sección Precisión del artículo de Wikipedia . Finalmente, si desea una discusión seria y profunda (y matemática) sobre los números / operaciones de punto flotante a nivel de máquina, intente leer el artículo citado a menudo Lo que todo informático debe saber sobre la aritmética de punto flotante .

Noldorin
fuente
1
¿Puede proporcionar un ejemplo, e de un número base 10 con el que se pierde precisión al convertir a la base 2?
Mark Cidade
@Mark: 1.000001 es un ejemplo, al menos según Jon Skeet. (Ver pregunta 3 de esta página: yoda.arachsys.com/csharp/teasers-answers.html )
Noldorin
25
@Mark: ejemplo muy simple: 0.1 es una fracción periódica en base 2, por lo que no se puede expresar con precisión en a double. Las computadoras modernas seguirán imprimiendo el valor correcto, pero solo porque "adivinen" el resultado, no porque realmente se exprese correctamente.
Konrad Rudolph
1
El Decimaltipo tiene 93 bits de precisión en la mantisa, en comparación con aproximadamente 52 para double. Sin embargo, desearía que Microsoft admitiera el formato IEEE de 80 bits, incluso si tuviera que rellenarse a 16 bytes; hubiera permitido un rango mayor que , doubleo Decimaluna velocidad mucho mejor que Decimal, soporte para operaciones trascendentales (por ejemplo, sin (x), log (x), etc.), y una precisión que, si bien no es tan buena como Decimalsería mucho mejor que double.
Supercat
@charlotte: Si lees mi publicación completa, verás que eso se explica.
Noldorin
59

Parece acertado con los beneficios de usar un tipo de punto flotante. Tiendo a diseñar para decimales en todos los casos, y confío en un generador de perfiles para que me haga saber si las operaciones en decimales están causando cuellos de botella o ralentizaciones. En esos casos, haré "down cast" para duplicar o flotar, pero solo lo haré internamente, y trataré cuidadosamente de manejar la pérdida de precisión limitando el número de dígitos significativos en la operación matemática que se realiza.

En general, si su valor es transitorio (no reutilizado), puede usar un tipo de coma flotante. El verdadero problema con los tipos de coma flotante son los siguientes tres escenarios.

  1. Está agregando valores de coma flotante (en cuyo caso los errores de precisión se combinan)
  2. Cree valores basados ​​en el valor de coma flotante (por ejemplo, en un algoritmo recursivo)
  3. Estás haciendo matemáticas con un número muy amplio de dígitos significativos (por ejemplo, 123456789.1 * .000000000000000987654321)

EDITAR

De acuerdo con la documentación de referencia sobre decimales de C # :

La palabra clave decimal indica un tipo de datos de 128 bits. En comparación con los tipos de coma flotante, el tipo decimal tiene una mayor precisión y un rango más pequeño, lo que lo hace adecuado para cálculos financieros y monetarios.

Entonces, para aclarar mi declaración anterior:

Tiendo a diseñar para decimales en todos los casos, y confío en un generador de perfiles para que me haga saber si las operaciones en decimales están causando cuellos de botella o ralentizaciones.

Solo he trabajado en industrias donde los decimales son favorables. Si está trabajando en motores de gráficos o gráficos, probablemente sea mucho más beneficioso diseñar para un tipo de coma flotante (flotante o doble).

El decimal no es infinitamente preciso (es imposible representar una precisión infinita para no integral en un tipo de datos primitivo), pero es mucho más preciso que el doble:

  • decimal = 28-29 dígitos significativos
  • doble = 15-16 dígitos significativos
  • flotante = 7 dígitos significativos

EDITAR 2

En respuesta al comentario de Konrad Rudolph , el ítem # 1 (arriba) es definitivamente correcto. La agregación de la imprecisión se compone de hecho. Consulte el siguiente código para ver un ejemplo:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

Esto genera lo siguiente:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

Como puede ver, a pesar de que estamos agregando desde la misma fuente constante, los resultados del doble son menos precisos (aunque probablemente se redondearán correctamente), y el flotador es mucho menos preciso, hasta el punto en que se ha reducido a solo Dos dígitos significativos.

Michael Meadows
fuente
1
El punto 1 es incorrecto. Los errores de precisión / redondeo solo ocurren en la fundición, no en los cálculos. Que es , por supuesto correcto que la mayoría de las operaciones matemáticas son inestables, lo que multiplica el error. Pero este es otro problema y se aplica igual para todos los tipos de datos de precisión limitada, en particular para el decimal.
Konrad Rudolph
1
@Konrad Rudolph, vea el ejemplo en "EDITAR 2" como evidencia del punto que estaba tratando de hacer en el ítem # 1. A menudo, este problema no se manifiesta porque la imprecisión positiva se equilibra con la imprecisión negativa, y desaparecen. el agregado, pero agregando el mismo número (como lo hice en el ejemplo) resalta el problema.
Michael Meadows
Gran ejemplo Acabo de mostrárselo a mis desarrolladores junior, los niños estaban asombrados.
Machado
Ahora puedes hacer lo mismo con 2/3 rds en lugar de 3/5. Debes aprender sobre el sistema de numeración sexagesimal que maneja 2/3 rds perfectamente bien.
gnasher729
1
@ gnasher729, el uso de 2 / 3rds en lugar de 3 / 5ths no se manejó perfectamente bien para los diferentes tipos. Curiosamente, el valor flotante rindió Single: 667660.400000000000mientras que el valor decimal rindió Decimal: 666666.7000000000. El valor flotante es un poco menos de mil sobre el valor correcto.
jhenninger
25

Use decimal para valores de base 10, por ejemplo, cálculos financieros, como otros han sugerido.

Pero doble es generalmente más preciso para valores calculados arbitrarios.

Por ejemplo, si desea calcular el peso de cada línea en una cartera, use el doble ya que el resultado casi sumará hasta el 100%.

En el siguiente ejemplo, doubleResult está más cerca de 1 que decimalResult:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

Así que de nuevo tomando el ejemplo de una cartera:

  • El valor de mercado de cada línea de la cartera es un valor monetario y probablemente se representaría mejor como decimal.

  • El peso de cada línea en la cartera (= Valor de mercado / SUM (Valor de mercado)) generalmente se representa mejor como doble.

Joe
fuente
6

Usa un doble o un flotador cuando no necesites precisión, por ejemplo, en un juego de plataformas que escribí, usé un flotador para almacenar las velocidades de los jugadores. Obviamente no necesito una súper precisión aquí porque eventualmente redondeo a un Int para dibujar en la pantalla.

FlySwat
fuente
3
La precisión es la ÚNICA ventaja de los decimales, esto es correcto. No debe preguntar cuándo debe usar números de coma flotante sobre decimales. Ese debería ser tu primer pensamiento. La pregunta es cuándo debe usar decimales (y la respuesta está aquí ... cuando la precisión importa).
Instance Hunter
3
@Daniel Straight, es divertido, pero tengo la opinión opuesta. Creo que usar un tipo menos preciso debido a sus características de rendimiento equivale a una optimización previa. Potencialmente, tendrá que pagar por esa optimización previa muchas veces antes de darse cuenta de su beneficio.
Michael Meadows
3
@ Michael Meadows, puedo entender este argumento. Sin embargo, algo a tener en cuenta es que una de las principales quejas con la optimización prematura es que los programadores no tienden a saber qué va a ser lento. Sin embargo, sabemos sin lugar a dudas que los decimales son más lentos que los dobles. Sin embargo, supongo que en la mayoría de los casos, la mejora del rendimiento no será notable para el usuario de todos modos. Por supuesto, en la mayoría de los casos, la precisión tampoco es necesaria. Je
Instance Hunter
La coma flotante decimal es en realidad MENOS precisa que la coma flotante binaria usando el mismo número de bits. La ventaja de Decimal es poder representar exactamente fracciones DECIMALES como 0.01 que son comunes en el cálculo financiero.
dan04
Bueno, esto no es del todo correcto :): en muchos juegos, los números de coma flotante pueden ser indeseables, debido al hecho de que no son consistentes. Ver aquí
BlueRaja - Danny Pflughoeft
4

En algunas Contabilidades, considere la posibilidad de utilizar tipos integrales en su lugar o en conjunto. Por ejemplo, supongamos que las reglas bajo las cuales opera requieren que cada resultado de cálculo se transfiera con al menos 6 decimales y el resultado final se redondeará al centavo más cercano.

Un cálculo de 1/6 de $ 100 rinde $ 16.66666666666666 ..., por lo que el valor realizado en una hoja de trabajo será de $ 16.666667. Tanto el doble como el decimal deberían dar ese resultado con precisión a 6 decimales. Sin embargo, podemos evitar cualquier error acumulativo llevando el resultado hacia adelante como un número entero 16666667. Cada cálculo posterior puede hacerse con la misma precisión y llevarse adelante de manera similar. Continuando con el ejemplo, calculo el impuesto a las ventas de Texas sobre esa cantidad (16666667 * .0825 = 1375000). Sumando los dos (es una hoja de trabajo corta) 1666667 + 1375000 = 18041667. Mover el punto decimal de nuevo nos da 18.041667, o $ 18.04.

Si bien este breve ejemplo no arrojaría un error acumulativo usando doble o decimal, es bastante fácil mostrar casos en los que simplemente calcular el doble o decimal y continuar acumularía un error significativo. Si las reglas bajo las cuales opera requieren un número limitado de decimales, almacenar cada valor como un número entero multiplicando por 10 ^ (número requerido de lugar decimal) y luego dividiendo por 10 ^ (número requerido de lugares decimales) para obtener el valor real El valor evitará cualquier error acumulativo.

En situaciones donde no se producen fracciones de centavos (por ejemplo, una máquina expendedora), no hay ninguna razón para usar tipos no integrales. Simplemente piense en ello como contar centavos, no dólares. He visto un código donde cada cálculo involucraba solo centavos enteros, ¡pero el uso de doble condujo a errores! Entero solo las matemáticas eliminaron el problema. Entonces, mi respuesta no convencional es, cuando sea posible, renunciar al doble y al decimal.

G DeMasters
fuente
3

Si necesita intercalar binarios con otros lenguajes o plataformas, es posible que necesite usar float o double, que están estandarizados.

Will Dean
fuente
2

Nota: esta publicación se basa en la información de las capacidades del tipo decimal de http://csharpindepth.com/Articles/General/Decimal.aspx y mi propia interpretación de lo que eso significa. Asumiré que Double es normal IEEE doble precisión.

Nota 2: el más pequeño y el más grande en esta publicación se refieren a la magnitud del número.

Pros de "decimal".

  • "decimal" puede representar exactamente números que pueden escribirse como fracciones decimales (lo suficientemente cortas), el doble no puede. Esto es importante en los libros de contabilidad financieros y similares, donde es importante que los resultados coincidan exactamente con lo que daría un humano haciendo los cálculos.
  • "decimal" tiene una mantisa mucho más grande que "doble". Eso significa que para valores dentro de su rango normalizado "decimal" tendrá una precisión mucho mayor que el doble.

Contras de decimal

  • Será mucho más lento (no tengo puntos de referencia, pero supongo que al menos un orden de magnitud tal vez más), el decimal no se beneficiará de ninguna aceleración de hardware y la aritmética requerirá una multiplicación / división relativamente costosa por potencias de 10 ( que es mucho más costoso que la multiplicación y la división por potencias de 2) para que coincida con el exponente antes de la suma / resta y para que el exponente vuelva al rango después de la multiplicación / división.
  • el decimal se desbordará antes que el doble. decimal solo puede representar números de hasta ± 2 96 -1. En comparación, el doble puede representar números de hasta ± 2 1024
  • el decimal se desbordará antes. Los números más pequeños representables en decimal son ± 10 -28 . En comparación, el doble puede representar valores hasta 2 -149 (aproximadamente 10 -45 ) si se admiten números subnómicos y 2 -126 (aproximadamente 10 -38 ) si no lo son.
  • decimal ocupa el doble de memoria que el doble.

Mi opinión es que debe usar el valor "decimal" por defecto para el trabajo con dinero y otros casos en los que hacer coincidir exactamente el cálculo humano es importante y que debe usar el doble como opción predeterminada el resto del tiempo.

lavado
fuente
2

Depende de para qué lo necesites.

Debido a que float y double son tipos de datos binarios, tiene algunas dificultades y errores en el número de rondas, por lo que double redondea 0.1 a 0.100000001490116, double también redondea 1/3 a 0.33333334326441. En pocas palabras, no todos los números reales tienen una representación precisa en tipos dobles

Afortunadamente, C # también admite la llamada aritmética decimal de coma flotante, donde los números se representan a través del sistema numérico decimal en lugar del sistema binario. Por lo tanto, la aritmética decimal de coma flotante no pierde precisión al almacenar y procesar números de coma flotante. Esto lo hace inmensamente adecuado para cálculos donde se necesita un alto nivel de precisión.

Neil Meyer
fuente
0

Use puntos flotantes si valora el rendimiento sobre la corrección.

Mark Brackett
fuente
66
Los números decimales no son más correctos, excepto en ciertos casos limitados que a veces son importantes (de ninguna manera siempre).
David Thornley
0

Elija el tipo en función de su aplicación. Si necesita precisión como en el análisis financiero, ha respondido su pregunta. Pero si su solicitud puede resolverse con una estimación, está bien con el doble.

¿Su aplicación necesita un cálculo rápido o tendrá todo el tiempo del mundo para darle una respuesta? Realmente depende del tipo de aplicación.

Gráfico hambriento? flotar o doble es suficiente. Análisis de datos financieros, ¿meteorito golpeando un planeta tipo de precisión? Esos necesitarían un poco de precisión :)

Kan
fuente
8
Los números decimales también son estimados. Se ajustan a las convenciones de la aritmética financiera, pero no hay ventaja en, digamos, los cálculos que involucran física.
David Thornley
0

El decimal tiene bytes más anchos, el doble es compatible de forma nativa con la CPU. El decimal es base 10, por lo que se produce una conversión de decimal a doble mientras se calcula un decimal.

For accounting - decimal
For finance - double
For heavy computation - double

Tenga en cuenta que .NET CLR solo admite Math.Pow (doble, doble). Decimal no es compatible.

.NET Framework 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);
Jeson Martajaya
fuente
0

Un valor doble se serializará a notación científica por defecto si esa notación es más corta que la visualización decimal. (por ejemplo, .00000003 será 3e-8) Los valores decimales nunca se serializarán en notación científica. Cuando se serializa para el consumo de una parte externa, esto puede ser una consideración.

Chris Klassen
fuente