Problemas com a Operação Modulo Do bc

Introdução

O bc, calculadora de mesa do linux, age um pouco estranho na hora de calcular o resto da divisão entre dois números. Esse “problema” também ocorre no dc, o que pode levar alguns programadores de shell script à loucura :-D

O problema

O operador % retorna o resto da divisão entre dois números:

$ echo 17%5 | bc
2

Até aí tudo bem. Se você utilizar a opção -l para acessar a biblioteca matemática, o resultado já sai diferente:

$ echo 17%5 | bc -l
0

O mesmo ocorre se você alterar a escala de zero para qualquer outro valor:

$ echo 'scale=3; 17%5' | bc
0

O problema é ainda pior se você está realizando cálculos com números decimais e depende do operador módulo:

$ echo 'scale=3; 3.1415 * (17%5)' | bc
0

sendo que o resultado esperado seria 3.1415*2=6.283.

Pelos exemplos, concluímos que quando números de ponto flutuantes são usados nas operações, o operador módulo não retorna o resto da divisão corretamente. O porquê disso pode ser conferido em [1], mas em poucas palavras, é porque o bc utiliza o operador de divisão para obter o resto. Para se calcular o resto entre a e b, o bc realiza:

a - (a/b)*b

Essa expressão retorna o resto da divisão se e somente se a divisão a/b for inteira. Ou seja, o bc depende completamente da divisão entre inteiros para retornar corretamente o resto da divisão. Alterando esse comportamento (alterando a scale ou usando a biblioteca matemática) a operação a/b é realizada em ponto flutuante e o resultado não é o esperado.

Por exemplo, sendo a=5 e b=2:

Divisão inteira

5 - (5/2)*2
5 - 2*2
5 - 4
1

Divisão não inteira (alterando scale ou usando a opção -l)

5 - (5/2)*2
5 - 2.5*2
5 - 5
0

Então, toda vez que você utilizar o operador % quando números em ponto flutuantes são usados, o resultado não sairá correto.

Solução

Infelizmente não encontrei uma solução built-in do bc a não ser implementar o operador módulo como função. A função abaixo calcula o módulo entre dois números inteiros independentemente se números de ponto flutuantes estão sendo usados na expressão:

define mod(x,y) {
  auto u,os; os=scale; scale=0
  u=x-(x/y)*y
  scale=os; return u;
}

salve como biblioteca.bc, por exemplo. Pra executar, chame a função toda vez que for realizar uma operação módulo, passando o nome do arquivo como parâmetro:

$ echo 'mod(5,2)' | bc biblioteca.bc
1

$ echo 'scale=3; mod(5,2)' | bc biblioteca.bc
1

$ echo 'mod(5,2)' | bc -l biblioteca.bc
1

Mesmo se scale for alterada ou a opção -l for usada, a função mod() sempre retornará o valor correto do módulo entre dois números inteiros.

Conclusão

O resultado do operador resto de divisão da calculadora bc (e também dc) depende se a divisão realizada é inteira ou não. Se a divisão não for inteira, quando a opção -l foi invocada ou o valor de scale foi alterado, o resultado não sairá corretamente. Para contornar esse problema, é preciso criar uma função mod() para retornar o valor correto.

Referências

[1] GNU BC: “modulo” % with scale other than 0 (Acessado em: Agosto/2013)
http://superuser.com/questions/31445/gnu-bc-modulo-with-scale-other-than-0

Deixe um comentário