web-dev-qa-db-fra.com

Mod de nombre négatif fait fondre mon cerveau

J'essaie de modifier un entier pour obtenir une position sur un tableau afin qu'il soit bouclé. Faire i % arrayLength fonctionne bien pour les nombres positifs, mais pour les nombres négatifs, tout 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

donc j'ai besoin d'une implémentation de

int GetArrayIndex(int i, int arrayLength)

tel 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

Je l'ai déjà fait auparavant, mais pour une raison quelconque, mon cerveau est en train de fondre aujourd'hui :( 

157
gormenghastly

J'utilise toujours ma propre fonction mod, définie par

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

Bien sûr, si vous êtes dérangé par le fait d'avoir deux appels à l'opération modulus, vous pouvez l'écrire comme suit:

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

ou leurs variantes.

Cela fonctionne parce que "x% m" est toujours dans la plage [-m + 1, m-1]. Donc, s'il est négatif, ajouter m le placera dans la plage positive sans modifier sa valeur modulo m.

243
ShreevatsaR

Veuillez noter que l'opérateur% C # et C++ n'est en réalité PAS un modulo, c'est le reste. La formule de modulo que vous souhaitez, dans votre cas, est la suivante:

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

Vous devez recoder ceci en C # (ou C++), mais c'est ainsi que vous obtenez modulo et non un reste.

66

Implémentation sur une seule ligne en utilisant % une seule fois:

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

La réponse de ShreevatsaR ne fonctionnera pas dans tous les cas, même si vous ajoutez "if (m <0) m = -m;", si vous tenez compte des dividendes/diviseurs négatifs.

Par exemple, -12 mod -10 sera 8, et il devrait être -2.

L'implémentation suivante fonctionnera pour les diviseurs/diviseurs positifs et négatifs et sera conforme à d'autres implémentations (notamment Java, Python, Ruby, Scala, Scheme, Javascript et la calculatrice de 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;
    }
}

Suite de test utilisant 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 };
        }
    }
5
dcastro

Pour les développeurs plus conscients des performances

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

Une petite comparaison de performance

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 ce qui concerne les coûts de performance de la fonte à uint jeter un oeil ici

5
Markus Cozowicz

Ajouter un peu de compréhension.

Par Définition euclidienne le résultat du mod doit toujours être positif.

Ex:

 int n = 5;
 int x = -3;

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

Sortie:

 -1
5
Abin Mathew

Ajoutez simplement votre module (arrayLength) au résultat négatif de% et tout ira bien.

4
starblue

Comparer deux réponses prédominantes

(x%m + m)%m;

et

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

Personne n’a en fait mentionné le fait que le premier peut lancer une OverflowException alors que le second ne le fera pas. Pire encore, avec un contexte non contrôlé par défaut, la première réponse peut renvoyer la mauvaise réponse (voir mod(int.MaxValue - 1, int.MaxValue) par exemple). La deuxième réponse semble donc non seulement être plus rapide, mais aussi plus correcte.

2
lilo0

J'aime le tour présenté par Peter N Lewis sur ce fil de discussion : "Si n a une portée limitée, vous pouvez obtenir le résultat souhaité en ajoutant simplement un multiple constant connu valeur absolue du minimum. "

Donc, si j’ai une valeur d en degrés et que je veux prendre 

d % 180f

et je veux éviter les problèmes si d est négatif, alors je fais simplement ceci:

(d + 720f) % 180f

Cela suppose que bien que d puisse être négatif, il est connu qu'il ne sera jamais plus négatif que -720.

1
RenniePet

Toutes les réponses ici fonctionnent très bien si votre diviseur est positif, mais ce n'est pas tout à fait complet. Voici mon implémentation qui renvoie toujours sur une plage de [0, b), telle que le signe de la sortie soit identique à celui du diviseur, ce qui permet d'utiliser des diviseurs négatifs comme point final de la plage de sortie.

PosMod(5, 3) renvoie 2
PosMod(-5, 3) renvoie 1
PosMod(5, -3) renvoie -1
PosMod(-5, -3) renvoie -2

    /// <summary>
    /// Performs a canonical Modulus operation, where the output is on the range [0, b).
    /// </summary>
    public static real_t PosMod(real_t a, real_t b)
    {
        real_t c = a % b;
        if ((c < 0 && b > 0) || (c > 0 && b < 0)) 
        {
            c += b;
        }
        return c;
    }

(où real_t peut être n'importe quel type de nombre)

0
Aaron Franke