La mod del numero negativo sta sciogliendo il mio cervello

Sto provando a modare un numero intero per ottenere una posizione dell’array in modo che giri in loop. Fare i % arrayLength funziona bene per i numeri positivi ma per i numeri negativi tutto va storto.

  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 

quindi ho bisogno di una implementazione di

 int GetArrayIndex(int i, int arrayLength) 

così

 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 

L’ho fatto prima, ma per qualche motivo mi sta sciogliendo il cervello oggi 🙁

Uso sempre la mia funzione mod , definita come

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

Naturalmente, se ti preoccupi di avere due chiamate all’operazione modulo, puoi scriverlo come

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

o sue varianti.

Il motivo per cui funziona è che "x% m" è sempre nell'intervallo [-m + 1, m-1]. Quindi se è negativo, aggiungendo m lo inserirà nell'intervallo positivo senza modificarne il valore modulo m.

Si noti che l’operatore% di C # e C ++ NON è in realtà un modulo, è resto. La formula per modulo che vuoi, nel tuo caso, è:

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

Devi ricodificarlo in C # (o C ++) ma questo è il modo in cui ottieni modulo e non un resto.

Implementazione su una sola riga usando % una sola volta:

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

La risposta di ShreevatsaR non funzionerà per tutti i casi, anche se si aggiunge “if (m <0) m = -m;", se si rappresentano dividendi / divisori negativi.

Ad esempio, -12 mod -10 sarà 8 e dovrebbe essere -2.

La seguente implementazione funzionerà sia per dividendi / divisori positivi che negativi e sarà conforms ad altre implementazioni (vale a dire, Java, Python, Ruby, Scala, Scheme, Javascript e Calcolatrice di 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; } } 

Test suite 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 }; } } 

Per gli sviluppatori più attenti alle prestazioni

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

Un piccolo confronto delle prestazioni

 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 

Per quanto riguarda il costo delle prestazioni del cast di uint, dai un'occhiata qui

Aggiungendo un po ‘di comprensione

Per definizione di Euclide il risultato mod deve essere sempre positivo.

Ex:

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

Produzione:

  -1 

Basta aggiungere il modulo (arrayLength) al risultato negativo di% e starai bene.

Mi piace il trucco presentato da Peter N Lewis su questo thread : “Se n ha un intervallo limitato, puoi ottenere il risultato desiderato semplicemente aggiungendo un multiplo costante noto di [il divisore] che è maggiore del valore assoluto del minimo.”

Quindi se ho un valore d che è in gradi e voglio prendere

 d % 180f 

e voglio evitare i problemi se d è negativo, allora invece faccio solo questo:

 (d + 720f) % 180f 

Ciò presuppone che sebbene d possa essere negativo, è noto che non sarà mai più negativo di -720.

Confrontando due risposte predominanti

 (x%m + m)%m; 

e

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

Nessuno ha effettivamente menzionato il fatto che il primo potrebbe lanciare un OverflowException mentre il secondo no. Ancora peggio, con il contesto deselezionato predefinito, la prima risposta può restituire la risposta sbagliata (vedi mod(int.MaxValue - 1, int.MaxValue) per esempio). Quindi la seconda risposta non solo sembra più veloce, ma anche più corretta.