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