Flutuante vs decimal no ActiveRecord

283

Às vezes, os tipos de dados do Activerecord me confundem. Erre, frequentemente. Uma das minhas eternas perguntas é, para um dado caso,

Devo usar :decimalou :float?

Eu sempre encontrei esse link, ActiveRecord:: decimal vs: float? , mas as respostas não são suficientemente claras para que eu tenha certeza:

Eu já vi muitos tópicos em que as pessoas recomendam flat out para nunca usar float e sempre usar decimal. Também vi sugestões de algumas pessoas para usar o float apenas para aplicações científicas.

Aqui estão alguns exemplos de casos:

  • Geolocalização / latitude / longitude: -45.756688, 120.5777777, ...
  • Rácio / porcentagem: 0.9, 1.25, 1.333, 1.4143, ...

Eu usei :decimalno passado, mas achei que lidar com BigDecimalobjetos em Ruby era desnecessariamente estranho em comparação com um flutuador. Também sei que posso usar :integerpara representar dinheiro / centavos, por exemplo, mas não se encaixa em outros casos, por exemplo, quando quantidades em que a precisão pode mudar com o tempo.

  • Quais são as vantagens / desvantagens de usar cada um?
  • Quais seriam algumas boas regras práticas para saber qual tipo usar?
Jonathan Allard
fonte

Respostas:

427

Lembro-me de meu professor do CompSci dizendo para nunca usar carros alegóricos como moeda.

A razão para isso é como a especificação IEEE define flutuadores no formato binário. Basicamente, ele armazena sinal, fração e expoente para representar um flutuador. É como uma notação científica para binário (algo como +1.43*10^2). Por isso, é impossível armazenar exatamente frações e decimais no Float.

É por isso que existe um formato decimal. Se você fizer isto:

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

enquanto que se você fizer

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

Portanto, se você estiver lidando com frações pequenas, como juros compostos ou talvez até geolocalização, eu recomendo o formato Decimal, pois no formato decimal 1.0/10é exatamente 0,1.

No entanto, deve-se notar que, apesar de serem menos precisos, os carros alegóricos são processados ​​mais rapidamente. Aqui está uma referência:

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

Responda

Use float quando não se importa muito com precisão. Por exemplo, algumas simulações e cálculos científicos precisam apenas de 3 ou 4 dígitos significativos. Isso é útil na troca de precisão por velocidade. Como eles não precisam tanto de precisão quanto de velocidade, usariam float.

Use decimal se estiver lidando com números que precisam ser precisos e que resumam o número correto (como juros compostos e coisas relacionadas a dinheiro). Lembre-se: se você precisar de precisão, use sempre decimal.

Iuri G.
fonte
Então, se eu entendi direito, float está na base 2, enquanto o decimal está na base 10? Qual seria um bom uso para flutuar? O que o seu exemplo faz e demonstra?
Jonathan Allard
1
Você não quer dizer em +1.43*2^10vez de +1.43*10^2?
Cameron Martin
46
Para futuros visitantes, o melhor tipo de dados para moeda é inteiro, não decimal. Se a precisão do campo for centavos, o campo será um número inteiro em centavos (não um decimal em dólares). Eu trabalhei no departamento de TI de um banco e foi assim que foi feito lá. Alguns campos tinham maior precisão (como centésimos de centavo), mas ainda eram números inteiros.
adg 24/08
1
@ adg está certo: bigdecimal também é uma má escolha para a moeda.
Eric Duminil
1
@ adg você está certo. Eu tenho trabalhado com alguns aplicativos contábeis e financeiros nos últimos anos e armazenamos todos os nossos campos de moeda em colunas inteiras. É muito mais seguro para esses casos.
Guilherme Lages Santos
19

No Rails 3.2.18,: decimal se transforma em: número inteiro ao usar o SQLServer, mas funciona bem no SQLite. Mudar para: float resolveu esse problema para nós.

A lição aprendida é "sempre use bancos de dados homogêneos de desenvolvimento e implantação!"

ryan0
fonte
3
Bom ponto, três anos depois de fazer Rails, eu concordo plenamente.
Jonathan Allard 22/10
3
"sempre use bancos de dados de desenvolvimento e implantação homogêneos!"
Zx1986
15

No Rails 4.1.0, eu enfrentei um problema ao salvar latitude e longitude no banco de dados MySql. Ele não pode salvar um número de fração grande com o tipo de dados flutuante. E mudo o tipo de dados para decimal e trabalhando para mim.

  mudança de definição
    change_column: cities,: latitude,: decimal,: precision => 15,: scale => 13
    change_column: cities,: longitude,: decimal,: precision => 15,: scale => 13
  fim
Rokibul Hasan
fonte
Eu salvo meu: latitude e: longitude como flutuadores no Postgres, e isso funciona muito bem.
23715 Scott Scott W
3
@ Robikul: sim, isso é bom, mas um exagero. decimal(13,9) é suficiente para latitude e longitude. @ ScottW: Não me lembro, mas se o Postgres usa flutuadores IEEE, ele apenas "funciona bem" porque você não teve problemas ... AINDA. É um formato insuficiente para latitude e longitude. Você acabará tendo erros nos dígitos menos significativos.
Lonny Eachus
@LonnyEachus, o que torna os flutuadores IEEE insuficientes para lat / longs?
21716 Alexander Suraphel
3
@AlexanderSuraphel Se você estiver usando latitude e longitude decimal, um flutuador IEEE estará suscetível a erros nos dígitos menos significativos. Portanto, sua latitude e longitude podem ter precisão de 1 metro, por exemplo, mas você pode ter erros de 100 metros ou mais. Isto é especialmente verdade se você os estiver usando em cálculos.
Lonny Eachus