Pregunta

Escribo aplicaciones financieras donde constantemente lucho contra 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 decimales y no son mayores de ~ 100,000. Tengo la sensación de que todos estos pueden ser representados como dobles sin errores de redondeo, pero nunca he estado seguro.

Seguiría adelante y cambiaría de decimales a dobles para obtener la ventaja de velocidad obvia, 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 salga El número que espero. (89.99 en lugar de 89.99000000001)

Preguntas:

  1. ¿La ventaja de velocidad es realmente tan grande como sugieren las pruebas ingenuas? (~ 100 veces)
  2. ¿Hay alguna manera de garantizar que la salida de ToString sea lo que quiero? ¿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 lo he implementado con decimal en este momento por razones obvias de protección, pero tarda ~ 3 horas solo en encenderse, los dobles reducirían drásticamente mi a tiempo ¿Hay alguna forma segura de hacerlo con dobles?

¿Fue útil?

Solución

  1. La aritmética de punto flotante casi siempre será significativamente más rápida porque es compatible directamente con el hardware. Hasta ahora, casi ningún hardware ampliamente utilizado admite aritmética decimal (aunque esto está cambiando, vea los comentarios).
  2. Las aplicaciones financieras deberían siempre usar números decimales, la cantidad de historias de terror derivadas del uso de coma flotante en las aplicaciones financieras es interminable, debería poder encontrar muchos 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 pase una cantidad significativa de tiempo procesando datos decimales, es probable que el impacto en su programa sea insignificante. Como siempre, realice el perfil adecuado antes de comenzar a preocuparse por la diferencia.

Otros consejos

Hay dos problemas separables aquí. Uno 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, es correcto 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 la aritmética escalada en la que opera en números multiplicados por 10 ^ 5. Entonces, por ejemplo, si quieres representar 23.7205 exactamente, lo representas como 2372050.

Veamos si hay suficiente precisión: la precisión doble le proporciona 53 bits de precisión. Esto es equivalente a más de 15 dígitos decimales de precisión. Entonces esto le permitiría cinco dígitos después del punto decimal y 10 dígitos antes del punto decimal, lo que parece suficiente para su aplicación.

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 difíciles, pero eso debería ser suficiente para comenzar.

Sin gastos generales para la adición, costo de multiplicar o dividir dobles.

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

Siempre use Decimal para cualquier cálculo financiero o siempre estará persiguiendo errores de redondeo de 1cent.

  1. Sí; La aritmética del software es realmente 100 veces más lenta que el hardware. O, al menos, es mucho más lento, y un factor de 100, más o menos un orden de magnitud, es correcto. En los viejos tiempos, cuando no se podía suponer que cada 80386 tenía un coprocesador de punto flotante 80387, entonces también tenía una simulación de software de punto flotante binario, y eso fue lento.
  2. No; estás viviendo en una tierra 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, cuartos, 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 una representación exacta en binario, no puede representar exactamente todos los valores decimales con valores binarios (porque 0.01 es un contraejemplo que no se puede representar exactamente, pero es representativo de una gran clase de números decimales que no se puede representar exactamente).

Por lo tanto, debe decidir si puede lidiar con el redondeo antes de llamar a ToString () o si necesita encontrar algún otro mecanismo que se ocupe de redondear sus resultados a medida que se convierten en una cadena. O puede continuar usando la aritmética decimal, ya que seguirá siendo precisa, y se volverá más rápido una vez que se lancen máquinas que admitan la nueva aritmética decimal IEEE 754 en hardware.

Referencia cruzada obligatoria: Lo que todo informático debe saber Acerca de la aritmética de coma flotante . Esa es una de las muchas URL posibles.

Información sobre aritmética decimal y el nuevo estándar IEEE 754: 2008 en este Speleotrove .

Simplemente use un valor largo y multiplíquelo por una potencia de 10. Una vez que haya terminado, divida por la misma potencia de 10.

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 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 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 imprimirá True !

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

Le remito a mi respuesta dada a esta pregunta .

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top