Float vs Decimal en ActiveRecord

283

A veces, los tipos de datos de Activerecord me confunden. Err, a menudo. Una de mis preguntas eternas es, para un caso dado,

¿Debo usar :decimalo :float?

A menudo me he encontrado con este enlace, ActiveRecord:: decimal vs: float? , pero las respuestas no son lo suficientemente claras para estar seguro:

He visto muchos hilos en los que la gente recomienda de plano que nunca use flotante y siempre use decimal. También he visto sugerencias de algunas personas para usar float solo para aplicaciones científicas.

Aquí hay algunos casos de ejemplo:

  • Geolocalización / latitud / longitud: -45.756688, 120.5777777, ...
  • Relación / porcentaje: 0.9, 1.25, 1.333, 1.4143, ...

Lo he usado :decimalen el pasado, pero descubrí que tratar con BigDecimalobjetos en Ruby era innecesariamente incómodo en comparación con un flotador. También sé que puedo usar :integerpara representar dinero / centavos, por ejemplo, pero no encaja en otros casos, por ejemplo, cuando las cantidades en las que la precisión podría cambiar con el tiempo.

  • ¿Cuáles son las ventajas / desventajas de usar cada uno?
  • ¿Cuáles serían algunas buenas reglas generales para saber qué tipo usar?
Jonathan Allard
fuente

Respuestas:

427

Recuerdo que mi profesor de CompSci me dijo que nunca usara flotadores como moneda.

La razón de esto es cómo la especificación IEEE define flotantes en formato binario. Básicamente, almacena signos, fracciones y exponentes para representar un Flotador. Es como una notación científica para binario (algo así como+1.43*10^2 ). Debido a eso, es imposible almacenar fracciones y decimales exactamente en Float.

Por eso hay un formato decimal. Si haces esto:

irb:001:0> "%.47f" % (1.0/10)
=> "0.10000000000000000555111512312578270211815834045" # not "0.1"!

mientras que si solo lo haces

irb:002:0> (1.0/10).to_s
=> "0.1" # the interprer rounds the number for you

Entonces, si está tratando con fracciones pequeñas, como intereses compuestos, o tal vez incluso geolocalización, le recomendaría encarecidamente el formato decimal, ya que en formato decimal 1.0/10 es exactamente 0.1.

Sin embargo, debe tenerse en cuenta que a pesar de ser menos precisos, los flotadores se procesan más rápido. Aquí hay un punto de referencia:

require "benchmark" 
require "bigdecimal" 

d = BigDecimal.new(3) 
f = Float(3)

time_decimal = Benchmark.measure{ (1..10000000).each { |i| d * d } } 
time_float = Benchmark.measure{ (1..10000000).each { |i| f * f } }

puts time_decimal 
#=> 6.770960 seconds 
puts time_float 
#=> 0.988070 seconds

Responder

Use flotador cuando no le interese demasiado la precisión. Por ejemplo, algunas simulaciones y cálculos científicos solo necesitan hasta 3 o 4 dígitos significativos. Esto es útil para compensar la precisión por la velocidad. Como no necesitan tanto la precisión como la velocidad, usarían flotación.

Use decimal si se trata de números que deben ser precisos y sumar el número correcto (como intereses compuestos y cosas relacionadas con el dinero). Recuerde: si necesita precisión, siempre debe usar decimal.

Iuri G.
fuente
Entonces, si entiendo correctamente, ¿flotante está en base-2 mientras que el decimal está en base-10? ¿Cuál sería un buen uso para flotar? ¿Qué hace y demuestra tu ejemplo?
Jonathan Allard
1
¿No quieres decir en +1.43*2^10lugar de +1.43*10^2?
Cameron Martin
47
Para futuros visitantes, el mejor tipo de datos para la moneda es entero, no decimal. Si la precisión del campo es centavos, entonces el campo sería un número entero en centavos (no un decimal en dólares). Trabajé en el departamento de TI de un banco y así fue como se hizo allí. Algunos campos tenían mayor precisión (como centésimas de centavo) pero aún eran enteros.
adg
1
@adg tiene razón: bigdecimal también es una mala elección para la moneda.
Eric Duminil
1
@adg tienes razón. He estado trabajando con algunas aplicaciones contables y financieras durante los últimos años y almacenamos todos nuestros campos de moneda en columnas enteras. Es mucho más seguro para estos casos.
Guilherme Lages Santos
19

En Rails 3.2.18,: decimal se convierte en: entero cuando se usa SQLServer, pero funciona bien en SQLite. El cambio a: float resolvió este problema por nosotros.

La lección aprendida es "¡siempre use bases de datos homogéneas de desarrollo e implementación!"

ryan0
fuente
3
Buen punto, 3 años después de hacer Rails, estoy totalmente de acuerdo.
Jonathan Allard
3
"siempre use bases de datos de desarrollo e implementación homogéneas!"
zx1986
15

En Rails 4.1.0, me he enfrentado a un problema al guardar latitud y longitud en la base de datos MySql. No puede guardar un número de fracción grande con el tipo de datos flotante. Y cambio el tipo de datos a decimal y funciona para mí.

  cambio de def
    change_column: cities,: latitude,: decimal,: precision => 15,: scale => 13
    change_column: cities,: longitude,: decimal,: precision => 15,: scale => 13
  final
Rokibul Hasan
fuente
Guardo mi: latitud y: longitud como flotadores en Postgres, y eso funciona bien.
Scott W
3
@Robikul: sí, eso es bueno, pero exagerado. decimal(13,9) es suficiente para la latitud y longitud. @ScottW: No recuerdo, pero si Postgres usa flotadores IEEE, solo "funciona bien" porque no ha tenido problemas ... TODAVÍA. Es un formato insuficiente para latitud y longitud. Eventualmente tendrá errores en los dígitos menos significativos.
Lonny Eachus
@LonnyEachus, ¿qué hace que los flotadores IEEE sean insuficientes para lat / longs?
Alexander Suraphel el
3
@AlexanderSuraphel Si está utilizando latitud y longitud decimales, un flotador IEEE es susceptible de errores en los dígitos menos significativos. Por lo tanto, su latitud y longitud pueden tener una precisión de 1 metro, por ejemplo, pero puede tener errores de 100 metros o más. Esto es especialmente cierto si los está utilizando en los cálculos.
Lonny Eachus