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?
Dedos Sangrentos
fonte
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]

david.pfx
fonte
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.

Brendan
fonte
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.

gnasher729
fonte