多くの人が指摘しているように、現在の C および C++ 標準では、 はおよびx % n
の値に対して実装定義ではなくなりました。が未定義の場合は未定義の動作です[1]。また、整数オーバーフローの場合の未定義の動作です。これは、 と の符号が異なる場合に発生する可能性があります。x
n
x / n
x - y
x
y
したがって、一般的な解決策の主な問題は、除算または減算で整数オーバーフローを回避することです。x
andy
が非負で正であることがわかっている場合、オーバーフローとゼロ除算は不可能であり、それが定義されn
ていると自信を持って言えます。(x - y) % n
残念ながら、x - y
負になる可能性があります。その場合、%
演算子の結果は負になります。
結果が正であることがわかっている場合、負の結果を修正するのは簡単n
です。無条件に追加n
して別のmodulo
操作を行うだけです。分岐よりも除算の方が速いコンピューターを使用していない限り、これが最善の解決策である可能性は低いです。
条件付きロード命令が利用できる場合 (最近ではかなり一般的です)、コンパイラはおそらく次のコードでうまく機能しますx,y ≥ 0 ∧ n > 0
。
((x - y) % n) + ((x >= y) ? 0 : n)
たとえば、gcc は私のコア I5 用に次のコードを生成します (ただし、古生代以外の Intel チップで動作するのに十分な汎用性があります)。
idivq %rcx
cmpq %rsi, %rdi
movl $0, %eax
cmovge %rax, %rcx
leaq (%rdx,%rcx), %rax
元気にブランチフリーです。(通常、条件付き移動は分岐よりもはるかに高速です。)
これを行う別の方法は次のとおりです (ただし、関数sign
を記述する必要があります)。
((x - y) % n) + (sign(x - y) & (unsigned long)n)
ここで、sign
は引数が負の場合はすべて 1 で、そうでない場合は 0 です。 sign の可能な実装の 1 つ ( bithacksから適応) は次のとおりです。
unsigned long sign(unsigned long x) {
return x >> (sizeof(long) * CHAR_BIT - 1);
}
これは移植性があります (負の整数値を unsigned にキャストすることが定義されています) が、高速シフトを欠くアーキテクチャでは遅くなる可能性があります。以前のソリューションよりも高速になる可能性は低いですが、YMMV. TIAS。
これらのどちらも、整数オーバーフローが発生する可能性がある一般的なケースでは正しい結果を生成しません。整数オーバーフローを処理するのは非常に困難です。(特に厄介なケースの 1 つは ですn == -1
。ただし、それをテストして を使用せずに 0 を返すことはできます%
。) また、負のモジュロの結果に対する好みを決定する必要がありますn
。個人的にx%n
は、 が 0 であるか、または と同じ符号を持つ定義を好みn
ます。
Tom Tanner によって提案された 3 モジュロ ソリューションは、そうでなく、オーバーフローしない場合にn
機能します。またはがの場合は失敗し、 の代わりに使用する単純な修正はが の場合に失敗します。絶対値が大きいケースは比較に置き換えることができますが、多くのコーナー ケースがあり、標準では 2 の補数演算が必要ないという事実によってより複雑になり、コーナー ケースが何であるかを簡単に予測することはできません。 [2]。-1
n + n
n == -1
x
y
INT_MIN
abs(n)
n
n
INT_MIN
n
最後に、いくつかの魅力的なソリューションは機能しません。の絶対値を取ることはできません(x - y)
。
(-z) % n == -(z % n) == n - (z % n) ≠ z % n
(z % n
たまたまでない限りn / 2
)
また、同じ理由で、モジュロの結果の絶対値を取ることはできません。
(x - y)
また、 unsignedにキャストすることはできません。
(unsigned)z == z + 2k (for some k) if z < 0
(z + 2k) % n == (z % n) + (2k % n) ≠ z % n
そうでもなければ(2k % n) == 0
[1]x/n
とx%n
はどちらも定義されていませんn==0
。しかし、 が「表現できない」場合 (つまり、整数のオーバーフローがあったx%n
場合) も未定義ですx/n
。2 の補数のマシン (つまり、関心のあるすべてのマシン) で発生します。この場合、 が未定義であるべき理由は明らかですが、 の場合は、その値が (数学的に) であるため、未定義であることがわずかに少なくなります。x
n == -1
x/n
x%n
0
[2] 浮動小数点演算の結果を予測することの難しさについて不平を言うほとんどの人は、真に移植可能な整数演算コードを書くことに多くの時間を費やしていません:)