負の数のModが私の脳を溶かしている


189

整数を変更して配列の位置を取得し、ループするようにしています。こう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

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


math.stackexchange.com/questions/519845/…の数学係数に関する説明を参照してください
PPC

回答:


281

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

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

もちろん、モジュラス演算を2回呼び出すのが面倒な場合は、次のように書くことができます。

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

またはその変種。

これが機能する理由は、「x%m」が常に[-m + 1、m-1]の範囲にあるためです。したがって、それが負の場合、mを追加すると、mを法としてその値を変更せずに正の範囲に入れられます。


7
注:完全な数論的な完全性のために、「if(m <0)m = -m;」と言う行を上部に追加することができます。この場合、「arrayLength」はおそらく常に正であるため、問題ではありません。
ShreevatsaR

4
mの値をチェックする場合は、ゼロも除外する必要があります。
billpg 2009

6
@RuudLenders:いいえ。x= -5およびm = 2の場合、r = x%mis -1であり、その後はr+mです1。whileループは必要ありません。重要なのは、(私が答えで書いたように)x%mは常に厳密により大きいため-mmポジティブにするために最大で1回追加する必要があるということです。
ShreevatsaR 2013年

4
@dcastro:-12 mod -10を8 にたい。これは数学で最も一般的な規則でありramoduloの代表を選択するとb、0≤r <| b |になる。
ShreevatsaR 2014年

8
+1。負の係数に対して個々の言語が何をするかは気にしません-「最小の非負の剰余」は数学的な規則性を示し、あいまいさを取り除きます。
Brett Hale、

80

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

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

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


21
「C ++の%演算子は実際には剰余ではないことに注意してください。それは剰余です。」おかげで、今では理にかなっていますが、なぜ負の数では正しく機能しないのか疑問に思っています。
leetNightshade

2
「C ++の%演算子は実際には剰余ではないことに注意してください。剰余です。」これは正確ではないと思います。剰余が剰余と異なる理由はわかりません。それは、Modulo Operation Wikipediaのページにも書かれています。プログラミング言語は負の数を異なる方法で処理するだけです。C#のモジュロ演算子は、「ゼロ」からの余りを明らかにカウントします(-9%4 = -1、4 * -2は-1の違いで-8であるため)一方、別の定義では-9%4を+3と見なします。 -4 * 3は-12、残りは+3です(Googleの検索機能などでは、バックエンド言語が不明です)。
タイレス、2014

18
タイレス、弾性率と残差には違いがあります。例: -21 mod 4 is 3 because -21 + 4 x 6 is 3. しかし、-21 divided by 4 gives -5remainder of -1。正の値の場合、違いはありません。ですから、これらの違いについて知っておいてください。そして、ウィキペディアのすべての時間を信用していない:)
ПетърПетров

2
なぜ誰かは剰余の代わりに剰余関数を使いたいのでしょうか?なぜ彼らは%残りを作ったのですか?
アーロンフランケ2018年

4
@AaronFranke-これは、商と剰余をすばやく生成する除算ハードウェアを備えた以前のCPUからの遺産です。これは、そのハードウェアが負の配当を与えたものです。言語は単にハードウェアを反映したものです。ほとんどの場合、プログラマーはプラスの配当で作業しており、この癖を無視しました。スピードが最優先でした。
ToolmakerSteve

16

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

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

1
これは正しいです?私はそれが誰にも受け入れられているとは思っていませんし、コメントもありません。例えば。mod(-10,6)は6を返します。それで正しいですか?それは4を返すべきではありませんか?
John Demetriou

3
@JohnDemetriouあなたの番号はどちらも間違っています:(A)2を返す必要があり、(B)2を返す; コードを実行してみてください。項目(A):mod(-10, 6)手動で見つけるには、答えが範囲内になるまで繰り返し6を加算または減算します[0, 6)。この表記は、「左を含み、右を含まない」ことを意味します。私たちのケースでは、6を2回追加して2を与えています。コードは非常にシンプルで、正しいことが簡単にわかります。最初に、n上記のように加算/減算と同等ですが、1 nから近づくと、1つのショートを停止します。マイナス面。その場合は修正します。そこに:コメント:)
エフゲニー・セルゲーエフ2017年

1
ちなみに、シングルを使用するの%が良い理由は次のとおりです。記事「高速なマネージコードを作成する:コストの把握」のマネージコードのコストの表を参照してください。使用は、表にリストされているのと同じように高価です。加算または減算よりも約36倍、乗算よりも約13倍高価です。もちろん、これがあなたのコードが何をしているかの中核でなければ、大したことはありません。%int div
Evgeni Sergeev 2017年

2
しかし%、特に簡単に予測できない場合は、1つのテストとジャンプよりも費用がかかりますか?
Medinoc 2017年

6

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

最初に、mod関数は通常、正の係数で呼び出されます(arrayLengthここで答えられる元の質問の変数は、おそらく負ではないことに注意してください)。したがって、関数は、負の係数で機能するように作成する必要はありません。(そのため、私は回答自体ではなく、私の回答のコメントで負の係数の扱いに言及しています。)(続き...)
ShreevatsaR 2014年

3
(... contd)次に、負の係数に対して何をするかは慣例の問題です。ウィキペディアなどを参照してください。「通常、数論では、常に正の剰余が選択されます」。これが私がそれを学んだ方法でもあります(BurtonのElementary Number Theoryで)。Knuthもそのように定義しています(具体的にr = a - b floor(a/b)は、常に正です)。コンピュータシステム、たとえばPascalやMapleの間でも、常にポジティブであると定義しています。
ShreevatsaR 2014年

@ShreevatsaRユークリッドの定義では結果は常に正であると述べていますが、最近のほとんどのmod実装は負の除数 "n"に対して[n + 1、0]の範囲の値を返すという印象を受けています、つまり、-12 mod -10 = -2です。私はGoogle電卓PythonRubyScalaを調査しましたが、すべてこの規則に従っています。
dcastro 2014年

また、リストに追加するには、SchemeJavascript
dcastro 14年

1
繰り返しますが、これはまだ良い読み物です。「常に肯定的な」定義(私の答え)は、ALGOL、Dart、Maple、Pascal、Z3などと一致しています。「除数の符号」(この答え)は、APL、COBOL、J、Lua、Mathematica、MSと一致しています。 Excel、Perl、Python、R、Ruby、Tclなど。AWK、bash、bc、C99、C ++ 11、C#、D、Eiffel、Erlang、Go、Javaのように、どちらも「被除数の符号」と一致しません。、OCaml、PHP、Rust、Scala、Swift、VB、x86アセンブリなど。1つの規則が「正しい」と他の規則が「間違っている」と主張する方法は本当にわかりません。
ShreevatsaR 2017年

6

理解を深める。

することにより、ユークリッド定義 MOD結果は常に正でなければなりません。

例:

 int n = 5;
 int x = -3;

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

出力:

 -1

15
私は混乱しています...あなたは結果が常に正であるべきだと言いますが、それから出力を-1
ジェフB

@JeffBridgman私はユークリッドの定義に基づいてそれを述べました。`余りには2つの選択肢があり、1つは負でもう1つは正であり、商についても2つの選択肢があります。通常、数論でthe positive remainder is always chosenはですが、プログラミング言語は、言語やaやnの記号に応じて選択します。[5] 標準PascalとAlgol68も負除数陽性余り(又は0)を得、そして場合、そのようなC90のようないくつかのプログラミング言語は、n個のいずれかの実装にそれを残したりnegative.`ある
ABIN

5

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

よりパフォーマンスを意識した開発者向け

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へのキャストのパフォーマンスコストについては、こちらをご覧ください。


3
-3 % 10-3または7のいずれかである必要があります。負ではない結果が必要であるため、7が答えになります。実装は3を返します。両方のパラメーターをuintキャストに変更して削除する必要があります。
私は、古いスタックオーバーフローを

5
符号なし算術はn、が2のべき乗である場合にのみ同等です。(uint)k & (n - 1)その場合、コンパイラーがまだそれを実行していない場合は、代わりに論理and()を使用できます(コンパイラーは多くの場合、これを理解するのに十分スマートです)。
j_schultz 2017

2

私はこのスレッドでピーターNルイスが提示したトリックが好きです。「nの範囲が限られている場合、[除数]の既知の定数倍数を追加するだけで、必要な結果を得ることができます。最小。"

したがって、度単位の値dがあり、

d % 180f

dが負の場合は問題を回避したいので、代わりに次のようにします。

(d + 720f) % 180f

これは、dが負になる可能性があるとしても、-720よりも負になることはないことがわかっていることを前提としています。


2
-1:一般的ではありません(そして、より一般的な解決策を与えるのは非常に簡単です)。
Evgeni Sergeev 2014

4
これは実際には非常に役立ちます。意味のある範囲がある場合、これにより計算が簡単になります。私の場合、math.stackexchange.com
questions / 2279751 /

正確には、これをdayOfWeek計算(既知の範囲-6〜+6)に使用するだけで、2つのを節約しました%
NetMage

@EvgeniSergeev +0私:OPの質問には答えませんが、より具体的なコンテキスト(ただし、質問のコンテキストでは)で役立つ可能性があります
Erdal G.

1

文書化されているC#の%演算子の動作とは逆の動作を期待しています-慣れ親しんでいる別の言語で動作するように動作することを期待している可能性があります。ドキュメント C#の状態で(強調鉱山):

整数型のオペランドの場合、a%bの結果は、a-(a / b)* bによって生成された値です。ゼロ以外の剰余の符号は、左側のオペランドの符号と同じです

必要な値は、1つの追加ステップで計算できます。

int GetArrayIndex(int i, int arrayLength){
    int mod = i % arrayLength;
    return (mod>=0) : mod ? mod + arrayLength;
}

1

dcastroの回答の1行の実装(他の言語に最も準拠):

int Mod(int a, int n)
{
    return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
}

%演算子の使用を維持したい場合(C#ではネイティブ演算子をオーバーロードできません):

public class IntM
{
    private int _value;

    private IntM(int value)
    {
        _value = value;
    }

    private static int Mod(int a, int n)
    {
        return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
    }

    public static implicit operator int(IntM i) => i._value;
    public static implicit operator IntM(int i) => new IntM(i);
    public static int operator %(IntM a, int n) => Mod(a, n);
    public static int operator %(int a, IntM n) => Mod(a, n);
}

ユースケース、どちらも機能します:

int r = (IntM)a % n;

// Or
int r = a % n(IntM);

0

ここでの答えはすべて、除数が正の場合はうまく機能しますが、完全ではありません。[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任意の数値タイプにすることができます)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.