web-dev-qa-db-ja.com

モジュロを使用して後方ラップアラウンド(「逆オーバーフロー」)を実行する式はありますか?

任意の整数入力[〜#〜] w [〜#〜]の場合、範囲[〜#〜] r [〜#〜]によって制限されます= [xy]、より良い用語がないための「オーバーフロー」、[〜#〜] w [〜#〜]over[〜#〜] r [〜#〜]is W % (y-x+1) + x。これにより、[〜#〜] w [〜#〜]yを超えると、ラップアラウンドします。

この原則の例として、カレンダーの月を反復するとします。

int this_month = 5;
int next_month = (this_month + 1) % 12;

ここで、両方の整数は0から11の間です。したがって、上記の式は整数を範囲[〜#〜] r [〜#〜]= [0,11]に「クランプ」します。式を使用するこのアプローチは、単純でエレガントであり、分岐を省略しているため有利です。

さて、同じことを逆方向にやりたいとしたらどうでしょうか?次の式が機能します。

int last_month = ((this_month - 1) % 12 + 12) % 12;

しかし、それは厄介です。どうすれば美化できますか?


tl; dr-式((x-1) % k + k) % kさらに単純化されますか?

注:他の言語ではモジュロ演算子の負のオペランドの処理が異なるため、C++タグが指定されています。

22
ayane

式は((x-1) + k) % kである必要があります。これにより、x = 0が11に適切にラップされます。一般に、1を超えてステップバックする場合は、モジュロ演算の最初のオペランドが> = 0になるように十分に追加する必要があります。

C++での実装は次のとおりです。

int wrapAround(int v, int delta, int minval, int maxval)
{
  const int mod = maxval + 1 - minval;
  if (delta >= 0) {return  (v + delta                - minval) % mod + minval;}
  else            {return ((v + delta) - delta * mod - minval) % mod + minval;}
}

これにより、0から11または1から12のラベルが付いた月を使用して、それに応じてmin_valおよびmax_valを設定することもできます。

この答えは非常に高く評価されているので、これは分岐のない改良版です。これは、初期値vminvalよりも小さい場合にも対応します。理解しやすいので、他の例を残します。

int wrapAround(int v, int delta, int minval, int maxval)
{
  const int mod = maxval + 1 - minval;
  v += delta - minval;
  v += (1 - v / mod) * mod;
  return v % mod + minval;
}

残っている唯一の問題は、minvalmaxvalより大きい場合です。必要に応じて、アサーションを自由に追加してください。

26
Fabian

k%kは常に0になります。何をしようとしているのか100%わかりませんが、先月を0から11までの範囲に固定する必要があるようです。

(this_month + 11) % 12

十分なはずです。

8
Enfyve

一般的な解決策は、必要な値を計算する関数を作成することです。

//Returns floor(a/n) (with the division done exactly).
//Let ÷ be mathematical division, and / be C++ division.
//We know
//    a÷b = a/b + f (f is the remainder, not all 
//                   divisions have exact Integral results)
//and
//    (a/b)*b + a%b == a (from the standard).
//Together, these imply (through algebraic manipulation):
//    sign(f) == sign(a%b)*sign(b)
//We want the remainder (f) to always be >=0 (by definition of flooredDivision),
//so when sign(f) < 0, we subtract 1 from a/n to make f > 0.
template<typename Integral>
Integral flooredDivision(Integral a, Integral n) {
    Integral q(a/n);
    if ((a%n < 0 && n > 0) || (a%n > 0 && n < 0)) --q;
    return q;
}

//flooredModulo: Modulo function for use in the construction
//looping topologies. The result will always be between 0 and the
//denominator, and will loop in a natural fashion (rather than swapping
//the looping direction over the zero point (as in C++11),
//or being unspecified (as in earlier C++)).
//Returns x such that:
//
//Real a = Real(numerator)
//Real n = Real(denominator)
//Real r = a - n*floor(n/d)
//x = Integral(r)
template<typename Integral>
Integral flooredModulo(Integral a, Integral n) {
    return a - n * flooredDivision(a, n);
}
6
Mankarse

Easy Peasy、最初のモジュール演算子は使用しないでください。不要です。

 int last_month = (this_month - 1 + 12) % 12;

これは一般的なケースです

この場合、次のように書くことができます11、しかし私はまだ-1 + 11達成したいことをより明確に示しているので、.

3
TabCam

あなたが私と同じ問題を抱えているかどうかはわかりませんが、私の問題は本質的に、すべての数値を特定の範囲に制限したいということでした。範囲が0〜6だったとすると、%7を使用すると、6より大きい数値は0以上に折り返されます。実際の問題は、ゼロ未満の数値が6に折り返されなかったことです。これに対する解決策があります(Xは数値範囲の上限、0は最小値です)。

if(inputNumber <0)//If this is a negative number
{
(X-(inputNumber*-1))%X; 
}
else
{
inputNumber%X;
}
2
fmwavesrgr8

通常のmodでは、パターン0...1112...2324...35などで繰り返されますが、-11...-1ではラップされないことに注意してください。つまり、2セットの動作があります。 1つは-infinity...-1からのもので、もう1つは0...infinityからの動作のセットです。

((x-1) % k + k) % k-11...-1を修正しますが、-23...-12を使用した通常のmodと同じ問題があります。つまり12個の追加の数値を修正しますが、無限にラップアラウンドすることはありません。 -infinity...-12とは1セットの動作があり、-11...+infinityとは異なる動作があります。

これは、オフセットに関数を使用している場合、バグのあるコードにつながる可能性があることを意味します。

真にラップアラウンドmodが必要な場合は、範囲全体-infinity...infinityをまったく同じ方法で処理する必要があります。

これを実装するためのより良い方法はおそらくありますが、ここに理解しやすい実装があります:

// n must be greater than 0
func wrapAroundMod(a: Int, n: Int) -> Int {
    var offsetTimes: Int = 0

    if a < 0 {
        offsetTimes = (-a / n) + 1
    }

    return (a + n * offsetTimes) % n
}
0
Senseful