web-dev-qa-db-ja.com

C / C ++で正のモジュロを取得する最速の方法

多くの場合、内側のループでは、「ラップアラウンド」方式で配列のインデックスを作成する必要があります。そのため、配列サイズが100で、コードが要素-2を要求する場合、要素98を指定する必要があります。 Pythonのように、これは_my_array[index % array_size]_で簡単に行うことができますが、何らかの理由でCの整数演算は(通常)一貫して切り捨てられるのではなくゼロに丸められ、その結果、そのモジュロ演算子は負の最初の引数が与えられると負の結果を返します。

indexが_-array_size_を下回らないことをよく知っています。これらの場合、my_array[(index + array_size) % array_size]を実行します。ただし、これが保証できない場合があります。これらの場合、常に正のモジュロ関数を実装する最も速い方法を知りたいと思います。分岐せずにそれを行うためのいくつかの「賢い」方法があります。

_inline int positive_modulo(int i, int n) {
    return (n + (i % n)) % n
}
_

または

_inline int positive_modulo(int i, int n) {
    return (i % n) + (n * (i < 0))
}
_

もちろん、これらをプロファイルして、システム上で最も高速なものを見つけることができますが、より良いものを逃したのではないか、別のマシンではマシンの高速なものが遅いのではないかと心配することはできません。

これを行うための標準的な方法、または私が見逃した巧妙なトリックはありますか?それは可能な限り最速の方法でしょうか?

また、私はそれがおそらく希望的観測であることを知っていますが、自動ベクトル化できるこれを行う方法があれば、それは驚くべきことです。

54
Nathaniel

私が学んだ標準的な方法は

inline int positive_modulo(int i, int n) {
    return (i % n + n) % n;
}

この関数は、基本的にabsなしの最初のバリアントです(実際、間違った結果を返します)。最適化コンパイラがこのパターンを認識し、「符号なしモジュロ」を計算するマシンコードにコンパイルできれば、驚くことはありません。

編集:

2番目のバリアントに進みます。まず、バグも含まれています-n < 0i < 0

この亜種は分岐したようには見えませんが、多くのアーキテクチャでは、i < 0は、条件付きジャンプにコンパイルされます。いずれにせよ、(n * (i < 0)) with i < 0? n: 0、乗算を回避します。さらに、boolをintとして再解釈することを避けるため、「クリーナー」です。

これら2つのバリアントのどちらが高速であるかについては、おそらくコンパイラとプロセッサアーキテクチャに依存します。ただし、これら2つの亜種のいずれよりも速い方法はないと思います。

70
Martin B

2のべき乗を法として、次のように機能します(2の補数表現を想定)。

return i & (n-1);
23
nneonneo

2の補数の符号ビット伝播を使用してオプションの加数を取得する古い方法:

int positive_mod(int i, int n)
{
    /* constexpr */ int shift = CHAR_BIT*sizeof i - 1;
    int m = i%n;
    return m+ (m>>shift & n);
}
6
jthill

より大きな型に昇格する余裕がある場合(およびより大きな型でモジュロを行う場合)、このコードは単一のモジュロを行い、ifを行いません:

int32_t positive_modulo(int32_t number, int32_t modulo) {
    return (number + ((int64_t)modulo << 32)) % modulo;
}
1
user15006

array[(i+array_size*N) % array_size]も実行できます。ここで、Nは正の引数を保証するのに十分な整数ですが、オーバーフローしないように十分に小さい整数です。

Array_sizeが一定の場合、除算なしでモジュラスを計算する手法があります。 2のべき乗のアプローチに加えて、2 ^ i%nを掛けたビットグループの加重和を計算できます。iは各グループの最下位ビットです。

例えば32ビット整数0xaabbccdd%100 = dd + cc * [2] 56 + bb * [655] 36 + aa * [167772] 16、最大範囲は(1 + 56 + 36 + 16)* 255 = 27795です。繰り返しアプリケーションと異なる細分化を使用すると、操作をいくつかの条件付き減算に減らすことができます。

一般的な慣行には、2 ^ 32/nの逆数による除算の近似も含まれます。通常、これはかなり広い範囲の引数を処理できます。

 i - ((i * 655)>>16)*100; // (gives 100*n % 100 == 100 requiring adjusting...)
1
Aki Suihkonen

2番目の例は最初の例よりも優れています。乗算はif/else演算よりも複雑な演算なので、これを使用します。

inline int positive_modulo(int i, int n) {
    int tmp = i % n;
    return tmp ? i >= 0 ? tmp : tmp + n : 0;
}
1
SkYWAGz

C/C++で正のモジュロを取得する最速の方法

次は速い? -他の人ほど高速ではないかもしれませんが、すべての人にとってシンプルで機能的に正しいです1 _a,b_-他とは異なり。

_int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}
_

特に_b < 0_の場合、他のさまざまな答えにはmod(a,b)の弱点があります。

_b < 0_についてのアイデアは ユークリッド除算 をご覧ください


_inline int positive_modulo(int i, int n) {
    return (i % n + n) % n;
}
_

_i % n + n_がオーバーフローすると失敗します(大きな_i, n_と考えてください)-未定義の動作。


_return i & (n-1);
_

2のべき乗としてnに依存します。 (答えがこれに言及しているのは公平です。)


_int positive_mod(int i, int n)
{
    /* constexpr */ int shift = CHAR_BIT*sizeof i - 1;
    int m = i%n;
    return m+ (m>>shift & n);
}
_

_n < 0_の場合に失敗することがよくあります。 e、g、positive_mod(-2,-3) --> -5


_int32_t positive_modulo(int32_t number, int32_t modulo) {
    return (number + ((int64_t)modulo << 32)) % modulo;
}
_

2つの整数幅を使用する義務。 (答えがこれに言及しているのは公平です。)
_modulo < 0_で失敗します。 positive_modulo(2, -3)-> -1.


_inline int positive_modulo(int i, int n) {
    int tmp = i % n;
    return tmp ? i >= 0 ? tmp : tmp + n : 0;
}
_

_n < 0_の場合に失敗することがよくあります。 e、g、positive_modulo(-2,-3) --> -5


1 例外:Cでは、_a%b_または_a/b_のように_a/0_がオーバーフローすると、_INT_MIN/-1_は定義されません。

0
chux