c# - ejemplos - La modificación del número negativo está derritiendo mi cerebro
operador % c# (8)
Estoy tratando de modificar un entero para obtener una posición de matriz para que gire en redondo. Hacer i % arrayLength
funciona bien para números positivos, pero para números negativos, todo va mal.
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
entonces necesito una implementación de
int GetArrayIndex(int i, int arrayLength)
tal 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
He hecho esto antes, pero por alguna razón está derritiendo mi cerebro hoy :(
Añadiendo algo de comprensión.
Según la definición euclidiana, el resultado del mod debe ser siempre positivo.
Ex:
int n = 5;
int x = -3;
int mod(int n, int x)
{
return ((n%x)+x)%x;
}
Salida:
-1
Implementación de línea única usando %
solo una vez:
int mod(int k, int n) { return ((k %= n) < 0) ? k+n : k; }
La respuesta de ShreevatsaR no funcionará para todos los casos, incluso si agrega "if (m <0) m = -m;", si representa dividendos / divisores negativos.
Por ejemplo, -12 mod -10 será 8, y debería ser -2.
La siguiente implementación funcionará tanto para dividendos / divisores positivos como negativos y cumple con otras implementaciones (a saber, Java, Python, Ruby, Scala, Scheme, Javascript y Google''s Calculator):
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<ArgumentOutOfRangeException>(() => 1.Mod(0));
}
public static IEnumerable<object[]> 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 };
}
}
Me gusta el truco presentado por Peter N Lewis en este hilo : "Si n tiene un rango limitado, entonces puedes obtener el resultado que deseas simplemente agregando un múltiplo constante conocido de [el divisor] que sea mayor que el valor absoluto del mínimo."
Entonces, si tengo un valor d que está en grados y quiero tomar
d % 180f
y quiero evitar los problemas si d es negativo, entonces simplemente hago esto:
(d + 720f) % 180f
Esto supone que, aunque d puede ser negativo, se sabe que nunca será más negativo que -720.
Para los desarrolladores más conscientes del rendimiento
uint wrap(int k, int n) ((uint)k)%n
Una pequeña comparación de rendimiento
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
En cuanto al costo de rendimiento de un elenco, eche un vistazo here
Siempre uso mi propia función mod
, definida como
int mod(int x, int m) {
return (x%m + m)%m;
}
Por supuesto, si le molesta tener dos llamadas a la operación de módulo, podría escribirla como
int mod(int x, int m) {
int r = x%m;
return r<0 ? r+m : r;
}
o variantes de los mismos
La razón por la que funciona es que "x% m" siempre está en el rango [-m + 1, m-1]. Entonces, si es negativo, sumar m lo colocará en el rango positivo sin cambiar su valor módulo m.
Simplemente agregue su módulo (arrayLength) al resultado negativo de% y estará bien.
Tenga en cuenta que el operador% C # y C ++ en realidad NO es un módulo, es resto. La fórmula para módulo que desea, en su caso, es:
float nfmod(float a,float b)
{
return a - b * floor(a / b);
}
Tienes que recodificar esto en C # (o C ++) pero esta es la forma en que obtienes módulo y no un resto.