254 votes

Le mode du nombre négatif me fait fondre le cerveau !

Duplicata de Comportement étrange des mods en Objective-C

J'essaie de modéliser un entier pour obtenir une position de tableau afin de pouvoir tourner en boucle. En faisant i % arrayLength fonctionne bien pour les nombres positifs, mais pour les nombres négatifs, tout va de travers.

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

int GetArrayIndex(int i, int arrayLength)

de telle sorte que

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

J'ai déjà fait ça avant mais pour une raison quelconque, ça me fait fondre le cerveau aujourd'hui :(

351voto

ShreevatsaR Points 21219

J'utilise toujours mes propres mod fonction, définie comme

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

Bien sûr, si ça vous dérange d'avoir deux à l'opération modulus, vous pourriez l'écrire comme suit

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

ou leurs variantes.

La raison pour laquelle cela fonctionne est que "x%m" est toujours compris dans l'intervalle [-m+1, m-1]. Donc, s'il est négatif, le fait de lui ajouter m le fera passer dans la plage positive sans changer sa valeur modulo m.

105voto

Veuillez noter que l'opérateur % du C# et du C++ n'est en fait PAS un modulo, mais un reste. La formule pour le modulo que vous voulez, dans votre cas, est :

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 un modulo et non un reste.

22voto

Evgeni Sergeev Points 1517

Mise en œuvre d'une seule ligne en utilisant % une seule fois :

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

7voto

dcastro Points 17978

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 donnera 8, alors qu'il devrait être -2.

L'implémentation suivante fonctionne pour les dividendes/diviseurs positifs et négatifs et est conforme aux autres implémentations (à savoir, 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 tests 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 };
        }
    }

5voto

RenniePet Points 2388

J'aime l'astuce présentée par Peter N Lewis sur ce fil : "Si n a une plage limitée, alors vous pouvez obtenir le résultat que vous voulez simplement en ajoutant un multiple constant connu de [le diviseur] qui est plus grand que la valeur absolue du minimum."

Donc si j'ai une valeur d qui est en degrés et je veux prendre

d % 180f

et je veux éviter les problèmes si d est négatif, alors à la place je fais juste ceci :

(d + 720f) % 180f

Cela suppose que, bien que d peut être négative, on sait qu'elle ne sera jamais plus négative que -720.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X