double - type - Decimal vs Doble Velocidad
variable decimal (7)
Escribo aplicaciones financieras en las que batalla constantemente la decisión de usar un doble frente a un decimal.
Todas mis matemáticas funcionan en números con no más de 5 lugares decimales y no son más grandes que ~ 100,000. Tengo la sensación de que todos estos pueden representarse como dobles de todos modos sin error de redondeo, pero nunca he estado seguro.
Continuaría y cambiaría de decimales a dobles para obtener la ventaja de velocidad obvia, excepto que al final del día, todavía utilizo el método ToString para transmitir precios a los intercambios, y necesito asegurarme de que siempre muestre el número I esperar. (89.99 en vez de 89.99000000001)
Preguntas:
- ¿La ventaja de la velocidad es realmente tan grande como lo sugieren las pruebas ingenuas? (~ 100 veces)
- ¿Hay alguna manera de garantizar que la salida de ToString sea lo que quiero? ¿Esto está asegurado por el hecho de que mi número siempre es representable?
ACTUALIZACIÓN: Tengo que procesar ~ 10 mil millones de actualizaciones de precios antes de que mi aplicación pueda ejecutarse, y he implementado con decimal en este momento por obvias razones protectoras, pero lleva ~ 3 horas simplemente encender, el doble reduciría drásticamente mi tiempo de activación . ¿Hay una manera segura de hacerlo con dobles?
- La aritmética de coma flotante casi siempre será significativamente más rápida porque es soportada directamente por el hardware. Hasta el momento, casi ningún hardware ampliamente utilizado admite la aritmética decimal (aunque esto está cambiando, ver comentarios).
- Las aplicaciones financieras siempre deben usar números decimales, la cantidad de historias de terror derivadas del uso de punto flotante en aplicaciones financieras es interminable, usted debería ser capaz de encontrar muchos de esos ejemplos con una búsqueda en Google.
- Si bien la aritmética decimal puede ser significativamente más lenta que la aritmética de punto flotante, a menos que gaste una cantidad significativa de tiempo procesando datos decimales, es probable que el impacto en su programa sea despreciable. Como siempre, haga los perfiles apropiados antes de comenzar a preocuparse por la diferencia.
- Sí; la aritmética de software es 100 veces más lenta que el hardware. O, al menos, es mucho más lento, y un factor de 100, dar o tomar un orden de magnitud, es lo correcto. En los viejos tiempos cuando no se podía suponer que cada 80386 tenía un coprocesador de coma flotante 80387, también se tenía simulación de software de punto flotante binario, y eso era lento.
- No; estás viviendo en un país de fantasía si crees que un punto flotante binario puro puede representar exactamente todos los números decimales. Los números binarios pueden combinar mitades, trimestres, octavos, etc., pero dado que un decimal exacto de 0.01 requiere dos factores de un quinto y un factor de un cuarto (1/100 = (1/4) * (1/5) * (1 / 5)) y dado que un quinto no tiene representación exacta en binario, no puede representar exactamente todos los valores decimales con valores binarios (porque 0.01 es un contraejemplo que no puede representarse exactamente, pero es representativo de una gran clase de números decimales que no puede ser representado exactamente).
Por lo tanto, debe decidir si puede manejar el redondeo antes de llamar a ToString () o si necesita encontrar algún otro mecanismo que se encargue de redondear los resultados a medida que se convierten en una cadena. O puede continuar utilizando la aritmética decimal, ya que seguirá siendo precisa, y se acelerará una vez que se lanzan las máquinas que admiten la nueva aritmética decimal IEEE 754 en el hardware.
Referencia cruzada obligatoria: lo que todo científico informático debería saber sobre la aritmética de coma flotante . Esa es una de las muchas posibles URL y conduce a un archivo PDF. Hay una versión HTML en Sun que aparentemente es una versión editada del mismo documento.
Información sobre aritmética decimal y el nuevo estándar IEEE 754: 2008 en este sitio de Speleotrove (material alojado previamente en IBM ).
Lo remito a mi respuesta dada a esta pregunta .
Use un archivo largo, almacene la cantidad más pequeña que necesita para rastrear, y muestre los valores correspondientes.
Los decimales siempre deben usarse para cálculos financieros. El tamaño de los números no es importante.
La forma más fácil de explicarlo es mediante algún código C #.
double one = 3.05;
double two = 0.05;
System.Console.WriteLine((one + two) == 3.1);
Ese bit de código imprimirá False aunque 3.1 sea igual a 3.1 ...
Lo mismo ... pero usando decimal:
decimal one = 3.05m;
decimal two = 0.05m;
System.Console.WriteLine((one + two) == 3.1m);
Esto ahora se imprimirá ¡ Verdadero !
Si desea evitar este tipo de problema, le recomiendo que se quede con los decimales.
Siempre use Decimal para cualquier cálculo financiero o siempre estará persiguiendo errores de redondeo de 1cent.
Simplemente use un largo y multiplique por una potencia de 10. Después de que haya terminado, divida por la misma potencia de 10.
Hay dos problemas separables aquí. Una es si el doble tiene suficiente precisión para contener todos los bits que necesita, y el otro es donde puede representar sus números exactamente.
En cuanto a la representación exacta, tienes razón en ser cauteloso, porque una fracción decimal exacta como 1/10 no tiene una contraparte binaria exacta. Sin embargo, si sabe que solo necesita 5 dígitos decimales de precisión, puede usar una aritmética a escala en la que opere en números multiplicados por 10 ^ 5. Entonces, por ejemplo, si quiere representar 23.7205 exactamente lo representa como 2372050.
Veamos si hay suficiente precisión: la precisión doble le da 53 bits de precisión. Esto es equivalente a más de 15 dígitos decimales de precisión. Así que esto le permitirá cinco dígitos después del punto decimal y 10 dígitos antes del punto decimal, lo que parece suficiente para su aplicación.
Yo pondría este código C en un archivo .h:
typedef double scaled_int;
#define SCALE_FACTOR 1.0e5 /* number of digits needed after decimal point */
static inline scaled_int adds(scaled_int x, scaled_int y) { return x + y; }
static inline scaled_int muls(scaled_int x, scaled_int y) { return x * y / SCALE_FACTOR; }
static inline scaled_int scaled_of_int(int x) { return (scaled_int) x * SCALE_FACTOR; }
static inline int intpart_of_scaled(scaled_int x) { return floor(x / SCALE_FACTOR); }
static inline int fraction_of_scaled(scaled_int x) { return x - SCALE_FACTOR * intpart_of_scaled(x); }
void fprint_scaled(FILE *out, scaled_int x) {
fprintf(out, "%d.%05d", intpart_of_scaled(x), fraction_of_scaled(x));
}
Probablemente haya algunos puntos difíciles, pero eso debería ser suficiente para comenzar.
Sin gastos adicionales, costo de una multiplicación o división de dobles.
Si tiene acceso a C99, también puede probar la aritmética de enteros escalados utilizando el tipo de entero int64_t
64 bits. Lo que es más rápido dependerá de su plataforma de hardware.