Mod de número negativo está derretendo meu cérebro

Eu estou tentando modificar um inteiro para obter uma posição de matriz para que ele dê um loop. Fazer i % arrayLength funciona bem para números positivos, mas para números negativos tudo dá errado.

  4 % 3 == 1 3 % 3 == 0 2 % 3 == 2 1 % 3 == 1 0 % 3 == 0 -1 % 3 == -1 -2 % 3 == -2 -3 % 3 == 0 -4 % 3 == -1 

então eu preciso de uma implementação de

 int GetArrayIndex(int i, int arrayLength) 

de tal modo que

 GetArrayIndex( 4, 3) == 1 GetArrayIndex( 3, 3) == 0 GetArrayIndex( 2, 3) == 2 GetArrayIndex( 1, 3) == 1 GetArrayIndex( 0, 3) == 0 GetArrayIndex(-1, 3) == 2 GetArrayIndex(-2, 3) == 1 GetArrayIndex(-3, 3) == 0 GetArrayIndex(-4, 3) == 2 

Eu já fiz isso antes, mas por algum motivo está derretendo meu cérebro hoje 🙁

Eu sempre uso minha própria function mod , definida como

 int mod(int x, int m) { return (x%m + m)%m; } 

Claro, se você está preocupado em ter duas chamadas para a operação de módulo, você pode escrever como

 int mod(int x, int m) { int r = x%m; return r<0 ? r+m : r; } 

ou variantes do mesmo.

A razão pela qual isso funciona é que "x% m" está sempre no intervalo [-m + 1, m-1]. Então, se é de todo negativo, adicionar m a ele irá colocá-lo na faixa positiva sem alterar seu valor módulo m.

Por favor, note que o operador% C # e C ++ não é, na verdade, um módulo, é o restante. A fórmula para o módulo que você quer, no seu caso, é:

 float nfmod(float a,float b) { return a - b * floor(a / b); } 

Você tem que recodificar isso em C # (ou C ++), mas esta é a maneira que você obtém módulo e não um resto.

Implementação de linha única usando % apenas uma vez:

 int mod(int k, int n) { return ((k %= n) < 0) ? k+n : k; } 

A resposta de ShreevatsaR não funcionará para todos os casos, mesmo se você adicionar “if (m <0) m = -m;", se você considerar dividendos / divisores negativos.

Por exemplo, -12 mod -10 será 8 e deverá ser -2.

A implementação a seguir funcionará para dividendos / divisores positivos e negativos e está em conformidade com outras implementações (ou seja, Java, Python, Ruby, Scala, Esquema, Javascript e Calculadora do Google):

 internal static class IntExtensions { internal static int Mod(this int a, int n) { if (n == 0) throw new ArgumentOutOfRangeException("n", "(a mod 0) is undefined."); //puts a in the [-n+1, n-1] range using the remainder operator int remainder = a%n; //if the remainder is less than zero, add n to put it in the [0, n-1] range if n is positive //if the remainder is greater than zero, add n to put it in the [n-1, 0] range if n is negative if ((n > 0 && remainder < 0) || (n < 0 && remainder > 0)) return remainder + n; return remainder; } } 

Suíte de teste usando xUnit:

  [Theory] [PropertyData("GetTestData")] public void Mod_ReturnsCorrectModulo(int dividend, int divisor, int expectedMod) { Assert.Equal(expectedMod, dividend.Mod(divisor)); } [Fact] public void Mod_ThrowsException_IfDivisorIsZero() { Assert.Throws(() => 1.Mod(0)); } public static IEnumerable GetTestData { get { yield return new object[] {1, 1, 0}; yield return new object[] {0, 1, 0}; yield return new object[] {2, 10, 2}; yield return new object[] {12, 10, 2}; yield return new object[] {22, 10, 2}; yield return new object[] {-2, 10, 8}; yield return new object[] {-12, 10, 8}; yield return new object[] {-22, 10, 8}; yield return new object[] { 2, -10, -8 }; yield return new object[] { 12, -10, -8 }; yield return new object[] { 22, -10, -8 }; yield return new object[] { -2, -10, -2 }; yield return new object[] { -12, -10, -2 }; yield return new object[] { -22, -10, -2 }; } } 

Para os desenvolvedores mais conscientes

 uint wrap(int k, int n) ((uint)k)%n 

Uma pequena comparação de desempenho

 Modulo: 00:00:07.2661827 ((n%x)+x)%x) Cast: 00:00:03.2202334 ((uint)k)%n If: 00:00:13.5378989 ((k %= n) < 0) ? k+n : k 

Quanto ao custo de desempenho do casting para uint, dê uma olhada aqui

Adicionando algum entendimento.

Por definição euclidiana, o resultado mod deve ser sempre positivo.

Ex:

  int n = 5; int x = -3; int mod(int n, int x) { return ((n%x)+x)%x; } 

Saída:

  -1 

Basta adicionar seu módulo (arrayLength) ao resultado negativo de% e você ficará bem.

Eu gosto do truque apresentado por Peter N Lewis neste tópico : “Se n tem um alcance limitado, então você pode obter o resultado que deseja simplesmente adicionando um múltiplo constante conhecido de [o divisor] que é maior que o valor absoluto do mínimo.”

Então, se eu tenho um valor d que está em graus e eu quero levar

 d % 180f 

e eu quero evitar os problemas se d for negativo, então eu apenas faço isso:

 (d + 720f) % 180f 

Isso pressupõe que embora d possa ser negativo, sabe-se que nunca será mais negativo que -720.

Comparando duas respostas predominantes

 (x%m + m)%m; 

e

 int r = x%m; return r<0 ? r+m : r; 

Ninguém realmente mencionou o fato de que o primeiro pode lançar uma OverflowException enquanto a segunda não. Pior ainda, com o contexto padrão desmarcado, a primeira resposta pode retornar a resposta errada (consulte mod(int.MaxValue - 1, int.MaxValue) por exemplo). Portanto, a segunda resposta não parece ser apenas mais rápida, mas também mais correta.