Diferentes tipos numéricos para diferentes necessidades em ruby
Tags: ruby, performance, beginners, optimization
Published: 2021-08-12 13:47:18

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
```

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)
```

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
```

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)
```

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)
```

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)
```

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)
```

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)
```

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**.