Que lógica é usada quando os projetistas da linguagem de programação decidem qual sinal o resultado da operação do módulo leva?


9

Passando pela operação do Módulo (a avenida que entrei enquanto explorava a diferença entre rememod ) me deparei com:

Em matemática, o resultado da operação do módulo é o restante da divisão euclidiana. No entanto, outras convenções são possíveis. Computadores e calculadoras têm várias maneiras de armazenar e representar números; portanto, sua definição da operação do módulo depende da linguagem de programação e / ou do hardware subjacente.

Questões:

  • Ao passar pela divisão euclidiana , descobri que o restante dessa operação é sempre positivo (ou 0). Que limitação do hardware do computador subjacente força os designers de linguagem de programação a diferir da matemática?
  • Toda linguagem de programação possui uma regra predefinida ou indefinida, segundo a qual o resultado da operação do módulo recebe seu sinal. Que lógica é adotada ao fazer essas regras? E se o hardware subjacente é a preocupação, as regras não devem mudar de acordo com isso, independentemente da linguagem de programação?

11
No meu código, quase sempre preciso do módulo, não do restante. Não faço ideia por que o restante é tão popular.
CodesInChaos

8
Relacionado Qual é a diferença? Restante vs Modulus - Blog do Eric Lippert (por um dos C # estilistas, mas acredito que ele se juntou a equipe após esta decisão foi tomada)
CodesInChaos

11
Se você continuar lendo o artigo da Wikipedia (além da parte que você citou), explica o que você citou muito bem. Sobre essa explicação, você está confuso?
Robert Harvey

11
Uma questão relacionada é qual dessas operações é mapeada diretamente para as instruções da CPU. Em c, sua implementação é definida, que se encaixa na filosofia c de mapear diretamente para o hardware em tantas plataformas quanto possível. Portanto, não especifica coisas que podem diferir entre as CPUs.
CodesInChaos

5
A programação @BleedingFingers geralmente usa divisão inteira que vai para zero, por exemplo (-3)/2 == -1. Essa definição pode ser útil. Quando você quer %ser consistente com essa divisão, x == (x/y)*y + x % yvocê acaba com a definição de %usado em C #.
CodesInChaos 31/01

Respostas:


6

O hardware de todos os computadores modernos é suficientemente poderoso para implementar operações modificadas de qualquer um dos signos sem impacto no desempenho (ou trivial). Esta não é a razão.

A expectativa comum da maioria das linguagens de computador é que (a div b) * b + (a mod b) = a. Em outras palavras, div e mod considerados juntos dividem um número em partes que podem ser reunidas novamente com segurança. Este requisito é explícito no padrão C ++. O conceito está intimamente relacionado à indexação de matrizes multidimensionais. Eu tenho usado frequentemente.

A partir disso, pode-se ver que div e mod preservarão o sinal de a se b for positivo (como geralmente é).

Algumas linguagens fornecem uma função 'rem ()' que está relacionada ao mod e tem alguma outra justificativa matemática. Eu nunca precisei usar isso. Veja, por exemplo, frem () no GNU C. [editado]


Eu acho que rem(a,b)é mais provável que seja, mod(a,b)se é positivo ou mod(a,b) + bnão.
user40989

3
(a div b) * b + (a mod b) = a- isso, muito mesmo. De fato, ao contrário de como a Wikipedia descreve estendê-lo para números negativos na divisão euclidiana (especialmente "O restante é o único dos quatro números que nunca podem ser negativos.") Me confunde porque sempre fui ensinado que o restante pode ser negativo em todas as aulas de matemática nesse nível.
Izkata 11/02

@ user40989: Eu disse que nunca tinha usado. Veja a edição!
David.pfx

4

Para programação normalmente você deseja X == (X/n)*n + X%n; portanto, como o módulo é definido depende de como a divisão inteira foi definida.

Com isso em mente, você está realmente perguntando " Que justificativa é usada quando os designers de linguagens de programação decidem como a divisão inteira funciona? "

Na verdade, existem cerca de 7 opções:

  • arredondado ao infinito negativo
  • arredondado ao infinito positivo
  • arredondar para zero
  • várias versões de "arredondar para o mais próximo" (com diferenças em como algo como 0,5 é arredondado)

Agora considere -( (-X) / n) == X/n. Eu gostaria que isso fosse verdade, pois qualquer outra coisa parece inconsistente (é verdade para ponto flutuante) e ilógica (uma causa provável de bugs e também uma otimização potencialmente perdida). Isso torna as duas primeiras escolhas para a divisão inteira (arredondamento para o infinito) indesejáveis.

Todas as opções "arredondar para o mais próximo" são um problema para a programação, especialmente quando você está fazendo algo como bitmaps (por exemplo offset = index / 8; bitNumber = index%8;).

Isso deixa o arredondamento em direção a zero como a opção "potencialmente mais sensata", o que implica que o módulo retorne um valor com o mesmo sinal que o numerador (ou zero).

Nota: Você também observará que a maioria das CPUs (todas as que eu conheço) fazem divisão inteira da mesma maneira "arredondada para zero". É provável que seja pelos mesmos motivos.


Mas a divisão truncante também tem suas próprias inconsistências: ela quebra (a+b*c)/b == a % be a >> n == a / 2 ** n, para a qual a divisão no piso tem um comportamento são.
dan04

Seu primeiro exemplo não faz sentido. Seu segundo exemplo é uma bagunça para os programadores: para positivo a e positivo n é consistente, para negativo a e positivo n depende de como o deslocamento à direita é definido (aritmético vs. lógico) e para negativo n é quebrado (por exemplo 1 >> -2 == a / 2 ** (-2)).
Brendan

O primeiro exemplo foi um erro de digitação: eu quis dizer (a + b * c) % b == a % b, ou seja, o %operador é periódico no divisor no dividendo, o que geralmente é importante. Por exemplo, com a divisão com piso, day_count % 7fornece o dia da semana, mas com a divisão de truncamento, isso é interrompido para datas anteriores à época.
dan04

0

Primeiro, repetirei que um módulo b deve ser igual a - b * (a div b) e, se uma linguagem não fornecer isso, você estará em uma terrível confusão matemática. Essa expressão a - b * (a div b) é na verdade quantas implementações calculam um módulo b.

Existem algumas razões possíveis. A primeira é que você deseja velocidade máxima, portanto, uma div b é definida como o que o processador usado fornecer. Se o seu processador possui uma instrução "div", então div div é o que quer que a instrução div faça (desde que não seja totalmente insana).

A segunda é que você deseja um comportamento matemático específico. Vamos primeiro assumir b> 0. É bastante razoável que você queira que o resultado de uma div b seja arredondado para zero. Então 4 div 5 = 0, 9 div 5 = 1, -4 div 5 = -0 = 0, -9 div 5 = -1. Isso fornece (-a) div b = - (a div b) e (-a) módulo b = - (a módulo b).

Isso é bastante razoável, mas não perfeito; por exemplo (a + b) div b = (a div b) + 1 não é válido, digamos se a = -1. Com um b fixo> 0, geralmente (b) valores possíveis para um tal que uma div b produz o mesmo resultado, exceto que existem 2b - 1 valores a de -b + 1 a b-1 onde uma div b é igual a 0 Isso também significa que um módulo b será negativo se a for negativo. Queremos que um módulo b seja sempre um número no intervalo de 0 a b-1.

Por outro lado, também é bastante razoável solicitar que, ao passar por valores sucessivos de a, um módulo b passe pelos valores de 0 a b-1 e comece com 0 novamente. E para solicitar que (a + b) div b seja (a div b) + 1. Para conseguir isso, você deseja que o resultado de uma div b seja arredondado para -in infinito, então -1 div b = -1. Mais uma vez, existem desvantagens. (-a) div b = - (a div b) não se mantém. Dividir repetidamente por dois ou por qualquer número b> 1 não resultará em 0.

Como existem conflitos, os idiomas terão que decidir qual conjunto de vantagens é mais importante para eles e decidir de acordo.

Para b negativo, a maioria das pessoas não consegue entender o que deve ser um div b e um módulo b, portanto, uma maneira simples é definir que a div b = (-a) div (-b) e a módulo b = (-a) módulo (-b) se b <0, ou qualquer que seja o resultado natural do uso do código para b positivo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.