DEV Community

BrunoSDias
BrunoSDias

Posted on

Diferentes tipos numéricos para diferentes necessidades em ruby

Ruby possui vários tipos númericos, como integers, floats, rationals e BigDecimal, sendo integers o mais simples deles.

Integer é o mais rápidos e simples tipo numérico, mas possui mais funcionalidade em comparação com muitas outras linguagens.
Por exemplo, em ruby ao tentar executar um bloco de código um certo número de vezes, em muitas outras linguagens seria necessário algo como um loop for ou utilizar uma range, já em Ruby, bastaria simplesmente chamar o método Integer#times:

10.times do
  #executed 10 times
end
Enter fullscreen mode Exit fullscreen mode

Algo em que muitos programadores Ruby se confundem é como divisões funcionam. Em Ruby, divisões entre inteiros (onde o numerador e o denominador são do tipo integer) são tratados similares a linguagem C, onde ele retorna apenas o quociente e remove todo o restante:

5/10
# => 0

7/3
# => 2 (Em algumas outras linguagens retornaria 2.333333333)
Enter fullscreen mode Exit fullscreen mode

Para que o restante apareça no resultado é necessário converter o numerador ou o denominador em tipos numéricos diferentes:

5/10r # or Rational(5, 10) or 5/10.to_r
#=>(1/2)

7.0/3
# => 2.333333333
Enter fullscreen mode Exit fullscreen mode

Em casos onde é necessária a parte fracionada, existem 3 opções de tipos numéricos: Float, Rational e BigDecimal.

Float

Float é o mais rápido dentre os três tipos, mas não são um tipo exato. Cálculos repetidos em valores do tipo Float podem causar problemas visíveis:

f = 1.1
1000.times do
  v += f
end

v
# => 1100.0000000000086 (o resultado deveria ser apenas 1100.0)
Enter fullscreen mode Exit fullscreen mode

Mas isso não ocorre em todos os cálculos repetidos:

f = 1.109375
v = 0.0

1000.times do
  v += f
end

v
# => 1109.375 (Dessa vez o cálculo está correto)
Enter fullscreen mode Exit fullscreen mode

Mas por que isso ocorre?
Isso acontece porque computadores armazenam isso no formato binário e não em decimal, e enquanto 0.109375 pode ser exatamente armazenado em binário, 1.1 não.

Rational

Rational, são mais lentos que Float (cerca de 2 a 6 vezes, dependendo do cálculo), mas como são exatos, você não precisa se preocupar com erros de cálculo:

f = 1.1r
v = 0.0r

1000.times do
  v += f
end

v
# => (1100/1) (Essa é a notação em que rationals são escritos)

f = 1.109375r
v = 0.0r

1000.times do
  v += f
end

v
# => (8875/8)

v.to_f
# => 1109.375 (É necessário converte-lo para Float para se tornar mais visivelmente amigável)
Enter fullscreen mode Exit fullscreen mode

Um bom principio é utilizar Rational sempre que precisar realizar cálculos que não sejam entre números inteiros e sejam necessárias respostas exatas.
Em casos onde não está havendo cálculos, onde você está apenas, por exemplo, comparando valores, talvez Float seja uma melhor opção.

BigDecimal

BigDecimal é similar a Rational, onde ele é exato na maioria dos casos, mas não é exato quando lidando com divisões que resultam em decimais repetitivos:

require 'bigdecimal'

v = BigDecimal(1)/3

v * 3
# => 0.999999999999999999e0 (resultado deveria ser 1)
Enter fullscreen mode Exit fullscreen mode

No entanto, além de divisões onde há repetidos decimais e exponenciação, valores BigDecimal são exatos:

require 'bigdecimal'

f = BigDecimal(1.1, 2)
v = BigDecimal(0)

1000.times do
  v += f
end

v
# => 0.11e4 (BigDecimal usa notação científica)

v.to_s('F')
# => "1100.0" (Pode gerar string visivelmente amigável para vizualização)
Enter fullscreen mode Exit fullscreen mode

Um dos problemas de se usar BigDecimal se á ao fato de ser necessário estipular a precisão inicial, por isso é comum inicializar valores BigDecimal de inteiros ou strings, para evitar a necessidade de manualmente especificar a precisão.

BigDecimal é significantemente mais lento que floats e rationals, e por isso, um bom princípio é utilizar BigDecimal apenas em casos em que é necessário lidar com outros sistemas que suportam tipos similares , como tipos de precisão numérica fixas em muitas base de dados, ou quando lidando com outras áreas de precisão fixas, como cálculos monetários.
Em outros casos, geralmente é melhor utilizar Float ou Rational.

Top comments (0)