web-dev-qa-db-ja.com

負の数のModが私の脳を溶かす

私は整数を変更して配列位置を取得しようとしているので、ループがループします。 i % arrayLengthを実行すると、正の数に対しては正常に機能しますが、負の数に対してはすべてうまくいきません。

 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

だから私はの実装が必要です

int GetArrayIndex(int i, int arrayLength)

そのような

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

私は前にこれをやったことがありますが、何らかの理由で今日は私の脳を溶かしています:(

168
gormenghastly

私は常に自分のmod関数を使用します。

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

もちろん、twoモジュラス操作の呼び出しに悩まされている場合は、次のように書くことができます。

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

またはそのバリアント。

動作する理由は、「x%m」が常に[-m + 1、m-1]の範囲にあるためです。そのため、すべてが負の場合、mを追加すると、mを法とする値を変更せずに正の範囲に配置されます。

257
ShreevatsaR

C#とC++の%演算子は実際にはモジュロではなく、剰余であることに注意してください。モジュロの式は、あなたの場合、次のとおりです。

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

これをC#(またはC++)で再コーディングする必要がありますが、これは剰余ではなくモジュロを取得する方法です。

%を1回だけ使用する単一行の実装:

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

いくつかの理解を追加します。

ユークリッド定義 によって、mod結果は常に正でなければなりません。

例:

 int n = 5;
 int x = -3;

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

出力:

 -1
6
Abin Mathew

ShreevatsaRの答えは、「if(m <0)m = -m;」を追加した場合でも、負の配当/除数を考慮しても、すべての場合に機能しません。

たとえば、-12 mod -10は8になり、-2になります。

次の実装は、正と負の両方の配当/除数で機能し、他の実装(つまり、Java、Python、Ruby、Scala、Scheme、Javascript、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;
    }
}

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

2つの主要な回答の比較

(x%m + m)%m;

そして

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

実際には、最初のものはOverflowExceptionをスローしても、2番目のものはスローしないという事実については誰も言及していません。さらに悪いことに、デフォルトの未チェックのコンテキストでは、最初の回答が間違った回答を返す場合があります(たとえば、mod(int.MaxValue - 1, int.MaxValue)を参照)。したがって、2番目の答えはより高速であるように見えるだけでなく、より正確であるようにも見えます。

4
lilo0

モジュラス(arrayLength)を%の負の結果に追加するだけで大​​丈夫です。

4
starblue

よりパフォーマンスを重視する開発者向け

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

小さなパフォーマンス比較

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

Uintへのキャストのパフォーマンスコストについては、外観を確認してください here

4
Markus Cozowicz

このスレッド :でピーターNルイスによって提示されたトリックが好きです。「nの範囲が制限されている場合、既知の定数の[除数]を追加するだけで、望む結果を得ることができます最小値の絶対値よりも大きい。」

だから、もし度である値dがあり、私が取りたい

d % 180f

dが負の場合、問題を回避したいので、代わりにこれを行います:

(d + 720f) % 180f

これは、dが負であっても、-720よりも負になることはないことがわかっていると仮定しています。

2
RenniePet

ここでの答えはすべて、除数が正であればうまく機能しますが、完全ではありません。これは、出力の符号が除数の符号と同じになるように、常に[0, b)の範囲で返す私の実装です。負の除数を出力範囲のエンドポイントとして使用できます。

PosMod(5, 3)2を返します
PosMod(-5, 3)1を返します
PosMod(5, -3)-1を返します
PosMod(-5, -3)-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;
    }

(ここで、real_tは任意の数値タイプにすることができます)

0
Aaron Franke