GNU BC: “modulo”% com escala diferente de 0

7

Se a escala for diferente de zero, os cálculos com%, como 3% 2 e 46% 4, tendem a produzir 0. Como o algoritmo é projetado com a escala diferente de 0?

bc
scale=10
print 4%3   // output 0
    
por KronoS 28.08.2009 / 18:28

5 respostas

10

O manual de comandos diz isto sobre como o BC calcula o módulo:

The result of the expression is the "remainder" and it is computed in the following way. To compute a%b, first a/b is computed to scale digits. That result is used to compute a - ( a/b ) * b to the scale of the maximum of scale+scale(b) and scale(a). If scale is set to zero and both expressions are integers this expression is the integer remainder function.

EDITAR: Eu olhei para o código-fonte do GNU BC e descobri que o operador mod estende o operador de divisão. Em outras palavras, o módulo é calculado como um subproduto da divisão. Depende da divisão inteira para calcular o módulo. Quando scale é definido, no entanto, a divisão de números inteiros não ocorre.

Tente isso no BC:

bc
scale = 0
print 5/2

scale = 5
print 5/2

você deve receber:

2        << Integer Division
2.50000  << NOT integer division!

Agora vamos inserir essas figuras da maneira que o BC faz. O manual diz que usa a- (a / b) * b para calcular. Vamos ligar nossos dois resultados, aquele resultante da divisão inteira e aquele com um scale diferente de 0.

a - ( a/b ) * b
5 - ( 2   ) * 2  = 1  << CORRECT!
5 - ( 2.5 ) * 2  = 0  << VERY WRONG!

Sem divisão inteira:

a - ( a/b ) * b == a - (  a  ) == 0

É por isso que a escala deve ser definida como 0 para o módulo funcionar corretamente.
A questão parece surgir do projeto do BC e como ele lida com números com uma 'escala'. Para que o módulo funcione corretamente precisamos de divisão inteira .

Existem outros muito ferramentas mais avançadas que são gratuitas e de código aberto para essa finalidade, e eu recomendo que você as use.

    
por 28.08.2009 / 19:22
3

Eu resolvi assim:

inteiro

defina int (x) { oldscale = escala; escala = 0; x = x / 1; scale = oldscale; retorno (x); }

modulo

define mod (x, y) { oldscale = escala; escala = 1000; x = x - y * int (x / y); scale = oldscale; retorno (x); }

HTH

    
por 21.02.2014 / 22:09
3

A resposta do user272970 é ótima. Aqui está um ajuste:

define int(x) { auto oldscale; oldscale=scale; scale=0; x=x/1; scale=oldscale; return( x ); }
define fmod(x,y) { auto oldscale; oldscale=scale; scale=1000; x = x - y * int(x/y); scale=oldscale; return( x ); }

Isso (usando auto oldscale ) torna oldscale local na função. Sem isso, definir oldscale in int() de fmod () sobrescreverá o oldscale que está tentando ser salvo em fmod() , deixando scale definido como 1000 em vez do que você tinha antes de chamar fmod() .

Eu adicionei essas funções a ~/.bcrc e defino a variável de ambiente BC_ENV_ARGS como ~/.bcrc . Isso carregará essas funções toda vez que você executar o bc. Portanto, agora posso executar fmod(x,y) sempre que estiver em bc sem ter que definir manualmente essas funções todas as vezes.

p.s. scale de 1000 pode ser um exagero na maioria dos casos

    
por 04.04.2014 / 21:10
0

Como outra resposta já mencionada, é o resultado da definição de a%b as (a-(a/b)*b) , avaliado no scale atual. Isto significa que se você quiser que ele funcione como um módulo inteiro, você precisa usá-lo com scale=0 .

No entanto, discordo que isso é "errado". É uma ferramenta potencialmente útil, especialmente para avaliar erros.

scale=5
1/3
> .33333
1%3
> .00001

O que estamos perdendo se representarmos 7/13 como o decimal de 4 dígitos .5384 ?

scale=4
7/13
> .5384
7%13
> .0008

Aparentemente 0.0008/13 .

E por último, porque não insiste em usar números inteiros, ele pode ser usado para extrair uma parte de um decimal.

scale=1
123.456/1
> 123.4
123.456%1
> .056
    
por 03.08.2016 / 18:57
0

O operador % é claramente definido no bc manual como [a] :

# Internal % operator definition:
define internalmod(n,d,s) { auto r,oldscale;
                            oldscale=scale; r=n/d;
                            s=max(s+scale(d),scale(n)); 
                            scale=s; r = n-(r)*d;
                            scale=oldscale; return(r) }

Assumindo que max foi definido como:

define max(x,y){ if(x>y){return(x)};return(y) }

Como essa longa definição é útil?

1. Inteiro restante .
 Usarei a função internalmod e o operador % para mostrar que são equivalentes para algumas operações abaixo

Se os números forem inteiros e a escala for definida como 0, é a função de resto inteiro.

$ bc <<<'n=17; d=3; scale=0; a=internalmod(n,d,scale); b=n%d; print a," ",b,"\n"'
2 2
$ bc <<<'n=17; d=6; scale=0; a=internalmod(n,d,scale); b=n%d; print a," ",b,"\n"'
5 5

Isso não é o mesmo que a função matemática mod. Eu vou resolver isso abaixo.

2. Decimal restante.
 Se o número n for um número decimal mais longo e modificarmos a escala, obteremos:

$ bc <<<'n=17.123456789;d=1; scale=0 ;a=internalmod(n,d,scale);b=n%d;print a," ",b,"\n"'
.123456789 .123456789

$ bc <<<'n=17.123456789;d=1; scale=3 ;a=internalmod(n,d,scale);b=n%d;print a," ",b,"\n"'
.000456789 .000456789

Note que aqui os 3 primeiros dígitos decimais foram removidos e o restante dado é do quarto dígito decimal.

$ bc <<<'n=17.123456789;d=1; scale=7 ;a=internalmod(n,d,scale);b=n%d;print a," ",b,"\n"'
.000000089 .000000089

Isso mostra que o restante é mais versátil por essa definição.

3. Mudança de escala  A mudança de escala é necessária porque o número d (divisor) pode ter mais dígitos decimais do que n . Nesse caso, mais decimais são necessários para ter um resultado mais preciso da divisão:

$ bc <<<'n=17.123456789; d=1.00000000001; scale=0; a=internalmod(n,d,scale);
         b=n%d; print a," ",scale(a)," -- ", b," ",scale(b),"\n"'
.12345678883 11 -- .12345678883 11

E, se a escala mudar:

$ bc <<<'n=17.123456789; d=1.00000000001; scale=5; a=internalmod(n,d,scale);
         b=n%d; print a," ",scale(a)," -- ", b," ",scale(b),"\n"'
.0000067888287655 16 -- .0000067888287655 16

Como pode ser visto acima, o valor da escala se expande para apresentar um resultado razoavelmente preciso da divisão para qualquer valor de n , d e scale .

Assumirei que, pela comparação entre o operador internalmod e o % , ambos provaram ser equivalentes.

4. Confusão . Tenha cuidado, porque brincar com o valor de d pode se tornar confuso:

$ bc <<<'n=17.123456789; d=10; scale=3; a=n%d; print a," ",scale(a),"\n"'
.003456789 9

E:

$ bc <<<'n=17.123456789; d=1000; scale=3; a=n%d; print a," ",scale(a),"\n"'
.123456789 9

Ou seja: o valor de d (acima de 1) modificará o efeito do valor do conjunto de escala.

Provavelmente, para valores de d diferentes de 1, você deve usar a escala = 0 (a menos que você realmente saiba o que está fazendo).

5. Mod matemática .
 Como estamos nos aprofundando nas funções mod, provavelmente devemos esclarecer o efeito real de % em bc . O operador % em bc está usando uma "divisão de truncamento". Um que arredonda para 0 . Isso é importante para valores negativos de n e / ou d :

$ bc <<<'scale=0; n=13; d=7; n%d; '
6

$ bc <<<'scale=0; n=13; d=-7; n%d; '
6

O sinal do restante segue o sinal do dividend .

$ bc <<<'scale=0; n=-13; d=7; n%d; '
-6

$ bc <<<'scale=0; n=-13; d=-7; n%d; '
-6

Embora um mod matemático correto deva dar um resto sempre positivo .

Para obter essa função mod (inteiro), use:

# Module with an always positive remainder (euclid division).
define modeuclid(x,div)  {  if(div!=int(div)){
                            "error: divisor should be an integer ";return(0)};
                            return(x - div*int(x/div))  }

E isso funcionará:

$ bc <<<"n=7.123456789; d=5; modeuclid(34.123456789,7)"
6.123456789

[a]

expr % expr
The result of the expression is the "remainder" and it is computed in the following way. To compute a%b, first a/b is computed to scale digits. That result is used to compute a-(a/b)*b to the scale of the maximum of scale+scale(b) and scale(a).
If scale is set to zero and both expressions are integers this expression is the integer remainder function.

Para o código bc que segue o ponto em que esta nota de rodapé foi introduzida para funcionar corretamente, defina um alias como:

$ alias bc='bc -l "$HOME/.func.bc"'

E crie um arquivo chamado $HOME/.func.bc que contenha (pelo menos):

# Internal % operator definition:
define internalmod(n,d,s) { auto r,oldscale;
                            oldscale=scale; r=n/d;
                            s=max(s+scale(d),scale(n)); 
                            scale=s; r = n-(r)*d;
                            scale=oldscale; return(r) } 
# Max function
define max(x,y){ if(x>y){return(x)};return(y) }

# Integer part of a number toward 0:  -1.99 -> -1, 0.99 -> 0
define int(x)        {  auto os;os=scale;scale=0;
                        x=sgn(x)*abs(x)/1;scale=os;return(x)  }

define sgn (x)       {  if (x<0){x=-1};if(x>0){x=1};return(x) };
define abs (x)       {  if (x<0) x=-x; return x }; 

# Module with an always positive remainder (euclid division).
define modeuclid(x,div)  {  if(div!=int(div)){
                            "error: divisor should be an integer ";return(0)};
                            return(x - div*int(x/div))  }

Uma função mod para qualquer número (inteiro ou não) pode ser definida como:

# Module with an always positive remainder (Euclid division).
define modeuclid(x,div)  {  div=abs(div);return(x - div*floor(x/div))  }

# Round down to integer below x (toward -inf).
define floor (x)         {  auto os,y;os=scale;scale=0;
            y=x/1;if(y>x){y-=1};scale=os;return(y) };

Esta definição é perfeitamente válida e correta por regras matemáticas, no entanto, pode se tornar bastante confuso ao tentar aplicá-la em casos reais, apenas dizendo.

    
por 28.10.2018 / 02:13

Tags