2010-06-14 16 views
18

¿Es correcto mi entendimiento de que con Ruby BigDecimal tipos (incluso con precisión y escala diferentes longitudes) debe calcular con precisión o debo prever engaños de punto flotante?Ruby BigDecimal cheque de cordura (punto flotante newb)

Todos mis valores dentro de una aplicación de Rails son BigDecimal tipo y estoy viendo algunos errores (tienen diferentes longitudes de decimales), con la esperanza de que sean solo mis métodos y no mis tipos de objetos.

Respuesta

31

Existen dos dificultades comunes cuando se trabaja con aritmética de coma flotante.

El primer problema es que los puntos flotantes de Ruby tienen una precisión fija. En la práctica, esto será 1) ningún problema para usted o 2) desastroso, o 3) algo intermedio. Considere lo siguiente:

# float 
1.0e+25 - 9999999999999999900000000.0 
#=> 0.0 

# bigdecimal 
BigDecimal("1.0e+25") - BigDecimal("9999999999999999900000000.0") 
#=> 100000000 

¡Una diferencia de precisión de 100 millones! Muy serio, ¿verdad?

Excepto que el error de precisión es solo aproximadamente 0.000000000000001% del número original. Realmente depende de usted decidir si esto es un problema o no. Pero el problema se elimina usando BigDecimal porque tiene una precisión arbitraria. Tu único límite es la memoria disponible para Ruby.

El segundo problema es que los puntos flotantes no pueden expresar todas las fracciones con precisión. En particular, tienen problemas con fracciones decimales, porque los flotantes en Ruby (y en la mayoría de los demás idiomas) son puntos flotantes binarios. Por ejemplo, la fracción decimal 0.2 es una fracción binaria eternamente repetitiva (0.001100110011...). Esto nunca se puede almacenar con precisión en un punto flotante binario, sin importar cuál sea la precisión.

Esto puede marcar una gran diferencia cuando se redondean los números. Considerar:

# float 
(0.29 * 50).round 
#=> 14 # not correct 

# bigdecimal 
(BigDecimal("0.29") * 50).round 
#=> 15 # correct 

A BigDecimal puede describir decimales fracciones con precisión. Sin embargo, hay fracciones que tampoco se pueden describir con precisión con una fracción decimal. Por ejemplo, 1/9 es una fracción decimal eternamente repetitiva (0.1111111111111...).

De nuevo, esto te morderá cuando redondeas un número. Considere:

# bigdecimal 
(BigDecimal("1")/9 * 9/2).round 
#=> 0 # not correct 

En este caso, el uso de puntos flotantes decimal todavía dar un error de redondeo.

Algunas conclusiones:

  • flotadores decimales son impresionantes si lo hace cálculos con fracciones decimales (dinero, por ejemplo).
  • Ruby's BigDecimal también funciona bien si necesita puntos flotantes de precisión arbitrarios, y realmente no le importa si son puntos flotantes decimales o binarios.
  • Si trabajas con datos (científicos), normalmente trabajas con números de precisión fijos; Los flotadores integrados de Ruby probablemente sean suficientes.
  • Nunca se puede esperar la aritmética con cualquier tipo de punto flotante para ser precisos en todas las situaciones.
+1

"En la práctica, esto será 1) no hay problema para ti o 2) desastroso, o 3) algo intermedio" - me encanta cómo lo has reducido :) –

Cuestiones relacionadas