2008-11-30 25 views
43

Escribo aplicaciones financieras en las que batalla constantemente la decisión de usar un doble frente a un decimal.decimal vs velocidad doble

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.

Me gustaría seguir y pasar de decimales a dobles para la ventaja obvia de la velocidad, excepto que al final del día, todavía uso el método ToString para transmitir precios a los intercambios, y necesito asegurarme de que siempre saca el número que espero. (89.99 en lugar de 89,99000000001)

Preguntas:

  1. es la ventaja de velocidad realmente tan grande como pruebas sugieren ingenuos? (~ 100 veces)
  2. ¿Hay alguna manera de garantizar que la salida de ToString sea la que deseo? ¿Esto está asegurado por el hecho de que mi número siempre es representable?

ACTUALIZACIÓN: Tengo que procesar unos 10 mil millones de actualizaciones de precios antes de mi aplicación puede funcionar, y yo he implementado con decimales en este momento por las razones de protección evidentes, pero se necesita ~ 3 horas sólo para encender, dobles haría reducir drásticamente mi tiempo de activación. ¿Hay una manera segura de hacerlo con dobles?

+4

Debido denominadores decimales son potencias de 10, y denominadores binarios son potencias de 2, incluso 1 lugar decimal no se puede representar sin error. Por ejemplo, 0.1 (una décima) no tiene equivalente exacto en binario, que es el mismo principio que un tercio que no tiene una representación exacta en decimal. –

+1

¿Cuál es su cuello de botella? ¿Dónde estás grabando todos tus ciclos de CPU? Sin mediciones sólidas, hay un buen momento en el que se está mirando algo totalmente erróneo. –

Respuesta

74
  1. La aritmética de coma flotante casi siempre será mucho más rápida porque es compatible directamente con el hardware. Hasta el momento, casi ningún hardware ampliamente utilizado admite la aritmética decimal (aunque esto está cambiando, ver comentarios).
  2. Las aplicaciones financieras deben siempre usar números decimales, el número de historias de terror derivadas del uso de coma flotante en aplicaciones financieras es interminable, debería ser capaz de encontrar muchos de esos ejemplos con una búsqueda en Google.
  3. Si bien la aritmética decimal puede ser significativamente más lenta que la aritmética de coma flotante, a menos que esté gastando una gran cantidad 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.
+7

+1: evitar la optimización prematura. Mida primero, optimice solo después de tener pruebas. –

+0

"hardware no ampliamente utilizado ..." IBM Mainframes (aún notablemente popular) tiene hardware decimal. –

+3

Las máquinas nuevas compatibles con el nuevo estándar de coma flotante IEEE 754 tendrán soporte aritmético decimal en el hardware. IBM Power6 es uno de esos, creo. –

17

Utilice siempre el valor decimal para cualquier cálculo financiero o siempre estará persiguiendo errores de redondeo de 1cent.

+6

+1: No pierdas tiempo en el rendimiento a menos que tengas pruebas de que es el paquete de matemáticas. –

+2

Esta no es una respuesta a las preguntas del OP. –

10
  1. 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.
  2. 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.

Obligatorio de referencia cruzada: What Every Computer Scientist Should Know About Floating-Point Arithmetic. 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 Speleotrove (material alojado previamente en IBM).

+0

Puedo estar confundido, pero pensé que 0.01 era 1/100 y 0.2 era 1/5? –

+0

1/100 necesita un divisor de 5 y una fracción binaria, o más exactamente, necesita dos factores de cinco: 1/100 = (1/4) * (1/5) * (1/5). El 1/4 se puede representar exactamente en un número de punto flotante binario; los factores 1/5 no pueden. –

5

Los decimales siempre se deben usar para los cálculos financieros. El tamaño de los números no es importante.

La manera más fácil de explicar es a través de algún código C#.

double one = 3.05; 
double two = 0.05; 

System.Console.WriteLine((one + two) == 3.1); 

Ese trozo de código imprimirá Falso a pesar de que 3.1 es igual a 3.1 ...

lo mismo ... pero usando decimales:

decimal one = 3.05m; 
decimal two = 0.05m; 

System.Console.WriteLine((one + two) == 3.1m); 

Esto hará ahora imprimir Verdadero!

Si desea evitar este tipo de problema, le recomiendo que se quede con los decimales.

+1

Lo que es realmente irritante es que los métodos .NET ToString() parecen ser incapaces de mostrar esta diferencia, incluso cuando se le pide que muestre hasta 20 cifras decimales. Si resta los números, puede ver que hay una diferencia de 4.44E-16 entre ellos ... pero no veo un 4 en ninguna parte de la cadena "3.10000000000000000000" salida por el método ToString. Si usa BitConverter.ToString (BitConverter.GetBytes (uno + dos)) y lo mismo para (3.1), entonces los bytes son realmente diferentes (CD-CC-CC-CC-CC-CC-08-40 vs. CC-CC-CC-CC-CC-CC-08-40), sin embargo double.ToString() no muestra esto! – Triynko

+2

Si se usa una escala adecuada y se tienen reglas de redondeo bien definidas, 'double' puede ser tan preciso como' decimal'. Si uno no tiene reglas precisas de redondeo, 'decimal' a menudo tendrá los mismos problemas que' double'. Por ejemplo, si tres personas compran cien artículos que se venden por un precio de "3 por $ 5", ¿cuánto dinero se debe recibir? – supercat

+0

Esto no responde ninguna de las preguntas que el OP hizo. –

21

Aquí hay dos problemas separables. 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, tiene 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 escala aritmética en la cual opera en números multiplicados por 10^5. Entonces, por ejemplo, si desea 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 hay algunos puntos ásperos pero eso debería ser suficiente para empezar.

Sin gastos generales para la suma, el costo de un multiplicar o dividir los dobles.

Si tiene acceso a C99, también puede probar la aritmética de enteros escalados utilizando el tipo de entero de 0 int64_t de 64 bits. Lo que es más rápido dependerá de su plataforma de hardware.

+0

¿Cuáles son los límites de multiplicación y división aquí? – Joe

+0

Es difícil de creer que esto no haya sido votado más alto, ya que es una respuesta mucho mejor para el OP, que buscaba mejoras de velocidad basadas en la observación de los cuellos de botella de perforación. Escalar y desincrustar es una buena respuesta que evita por completo el error de redondeo al aumentar la velocidad 100X. –

1

Lo remito a mi respuesta dada a this question.

Use una fuente larga, almacene la cantidad más pequeña que necesita rastrear y visualice los valores en consecuencia.

7

Sólo tiene que utilizar un largo y multiplique por una potencia de 10. Después de que haya terminado, se divide por la misma potencia de 10.

Cuestiones relacionadas