負の数のモジュロ演算


190

Cプログラムで私は以下の操作を試みました(動作を確認するだけです)

 x = 5 % (-3);
 y = (-5) % (3);
 z = (-5) % (-3); 

printf("%d ,%d ,%d", x, y, z); 

(2, -2 , -2)gccのように出力しました。毎回良い結果を期待していた。係数は負にできますか?誰かがこの動作を説明できますか?



回答:


167

C99 では、が次の場合a/bに表現可能であることが必要です。

(a/b) * b + a%b等しいa

これは論理的に理にかなっています。正しい?

これが何につながるか見てみましょう:


例A 5/(-3)-1

=> (-1) * (-3) + 5%(-3) =5

これ5%(-3)は、2の場合にのみ発生します。


例B (-5)/3-1

=> (-1) * 3 + (-5)%3 =-5

これが発生するの(-5)%3は、-2


1
コンパイラーは十分にスマートで、別の符号なしの符号なしモジュロが常に正であることを検出しますか?現在(まあ、GCC 5.2)コンパイラは、両方のオペランドがuint32_t以上の場合でも、「%」が「符号なし」ではなく「int」を返すと考えているようです。
Frederick Nord

@FrederickNord その動作を示す例はありますか?
chux-モニカを2017年

10
あなたが説明するのはmodの通常のint(a / b)(truncate)の説明であることを理解してください。ただし、ルールがfloor(a / b)(Knuth)である可能性もあります。Knuthの場合-5/3-2、modは1になります。要するに、1つのモジュールは被除数記号(切り捨て)に続く記号を持ち、もう1つのモジュールは除数記号(Knuth)に続く記号を持ちます。
アイザック

1
これは、C標準がまったく私が望むものではない場合です。私は、ゼロまたは負の剰余番号にTRUNCATEを望んでいませんが、多くの場合、Cの周りの仕事に反対し、必要性を望むことがない
ジョー・

141

%C の演算子はモジュロ演算子ではなく、剰余演算子です。

モジュロ演算子と剰余演算子は、負の値に関して異なります。

剰余演算子では、結果の符号は被除数の符号と同じですが、モジュロ演算子では、結果の符号は除数と同じです。

Cは次のように%操作を定義しますa % b

  a == (a / b * b) + a % b

/向かって切り捨て付き整数の除算0。これ0は、%を剰余演算子ではなく剰余演算子として定義する(負の無限大ではなく)に対して行われる切り捨てです。


7
剰余は、定義により、モジュロ演算の結果です。剰余演算などは存在しないため、剰余演算子などは存在しないはずです。これはモジュロと呼ばれます。
gronostaj 2015年

41
CSにない@gronostaj。両者が(二つの異なる演算子を定義することはHaskellやスキームのような、より高いレベルの言語でのルックremaindermoduloスキームに、remそしてmodHaskellで)。これらの演算子の仕様は、除算の方法、つまり0または負の無限大への切り捨てについて、これらの言語で異なります。ちなみに、C標準で%モジュロ演算子を呼び出すことはなく、単に%演算子と名付けてい ます。
ouah

2
C のremainder 関数と混同しないでください。この関数は、部門で最も近い丸めのセマンティクスでIEEE剰余を実装します
Eric

67

C99仕様に基づく: a == (a / b) * b + a % b

計算する関数を書くことができます(a % b) == a - (a / b) * b

int remainder(int a, int b)
{
    return a - (a / b) * b;
}

モジュロ演算の場合、次の関数を使用できます(を想定b > 0

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

私の結論はa % b、Cでは剰余演算であり、剰余演算ではないということです。


3
bが負の場合、これは正の結果を与えません(実際にはrb両方の負の場合、結果は未満になります-b)。使用できるすべての入力で確実な結果を得るために、r + abs(b)またはbs記号と一致させるために、r*b < 0代わりに条件を変更できます。
マーティンエンダー

@MartinEnder r + abs(b)はUB b == INT_MINです。
chux-モニカを復活させる'09 / 09/27

60

数値が負かどうかを確認する必要はないと思います。

正のモジュロを見つける簡単な関数は次のようになります-

編集:仮定N > 0N + N - 1 <= INT_MAX

int modulo(int x,int N){
    return (x % N + N) %N;
}

これは、xの正のと負の値の両方で機能します。

オリジナルPS:また@chuxによって尖ったアウトなど、あなたのxおよびNは、それぞれINT_MAX-1とINT_MAXのようなものに達するだけで交換することができる場合intlong long int

また、それらがlong longの制限を超えている場合(つまり、LLONG_MAXに近い場合)、ここで他の回答で説明されているように、正のケースと負のケースを別々に処理する必要があります。


1
の場合N < 0、結果はのように負になる可能性があることに注意してくださいmodulo(7, -3) --> -2。未定義の動作である数学をx % N + Nオーバーフローさせることもできintます。たとえばmodulo(INT_MAX - 1,INT_MAX)、-3になる可能性があります。
chux-モニカを復活させる'09 / 09/27

はい。その場合long long int、単にを使用するか、または(単純さを失うことを犠牲にして)否定的なケースを個別に処理します。
Udayraj Deshmukh 2018

9

他の答えはC99以降で説明されており、負のオペランドを含む整数の除算は常にゼロに切り捨てられます

C89では、結果の上方または下方への丸めは実装定義であることに注意してください。すべての標準で(a/b) * b + a%b等しいためa%負のオペランドを使用した結果もC89で実装定義されます。


5

係数は負にできますか?

%Euclidean_divisionの後ではなく、除算後の剰余演算子であるため、負の値にすることができます。C99以降、結果は0、負または正になる場合があります。

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  

モジュロたかったOPは、古典的であるユークリッド法として、ではありません%

毎回良い結果を期待していた。

定義されるたびa/bに明確に定義されるユークリッドモジュロを実行するに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;
}

modulo_Euclidean( 7,  3) -->  1  
modulo_Euclidean( 7, -3) -->  1  
modulo_Euclidean(-7,  3) -->  2  
modulo_Euclidean(-7, -3) -->  2   

2

Modulo演算の結果は分子の符号に依存するため、yおよびzに対して -2が得られます。

これがリファレンスです

http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_14.html

整数部

このセクションでは、整数除算を実行するための関数について説明します。GNU Cでは「/」演算子は常にゼロに丸められるため、これらの関数はGNU Cライブラリでは冗長です。ただし、他のCの実装では、「/」は負の引数で異なる丸めが行われる場合があります。divおよびldivは、商をゼロに丸める方法を指定するので便利です。残りは分子と同じ符号を持っています。


5
あなたはANSI Cに関するテキストを参照しています。これはCのかなり古い規範です。テキストがANSI Cに関して正しいかどうかはわかりませんが、C99に関しては間違いありません。C99§6.5.5では、整数除算は常にゼロに向かって切り捨てられるように定義されています。
Palec、2014

2

これらの慣習が由来する数学では、剰余算術が肯定的な結果をもたらすはずであるという主張はありません。

例えば。

1 mod 5 = 1ですが、-4にすることもできます。つまり、1/5は0から余り1を、5から-4を返します(両方の係数5)。

同様に、-1 mod 5 = -1ですが、4に等しくすることもできます。つまり、-1 / 5は、0からの剰余-1または-5からの剰余-1を生成します。(5の両方の要因)

詳細については、数学の等価クラスを参照してください。


等価クラスは別の概念であり、モジュロは非常に厳密な方法で定義されます。2つの整数ab、があるとしb <> 0ます。ユークリッド除算の定理によれば、整数の正確に一つのペアが存在するm、とします。これは、(数学的な)モジュロ演算の結果であり、定義により負ではありません。ウィキペディアの詳細とリンク:en.wikipedia.org/wiki/Euclidean_divisionra = m * b + r0 <= r < abs( b )r
Ister

それは真実ではありません。1 mod 5は常に1です。1でもある-4 mod 5可能性がありますが、それらは異なるものです。
FelipeC

2

C99標準、セクション6.5.5乗法演算子によれば、以下が必要です。

(a / b) * b + a % b = a

結論

C99によれば、剰余演算の結果の符号は被除数の符号と同じです。

いくつかの例を見てみましょう(dividend / divisor):

配当のみマイナスの場合

(-3 / 2) * 2  +  -3 % 2 = -3

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

除数のみが負の場合

(3 / -2) * -2  +  3 % -2 = 3

(3 / -2) * -2 = 2

(3 % -2) must be 1

除数と配当の両方が負の場合

(-3 / -2) * -2  +  -3 % -2 = -3

(-3 / -2) * -2 = -2

(-3 % -2) must be -1

6.5.5乗法演算子

構文

  1. 乗法式:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

制約

  1. オペランドのそれぞれは、算術型を持つ必要があります。演算子のオペランドは整数型でなければなりません。

意味論

  1. 通常の算術変換は、オペランドに対して実行されます。

  2. 二項*演算子の結果は、オペランドの積です。

  3. /演算子の結果は、最初のオペランドを2番目のオペランドで除算した商です。演算子の結果が余りです。どちらの演算でも、第2オペランドの値がゼロの場合の動作は未定義です。

  4. 整数を除算すると、/演算子の結果は、小数部分が破棄された代数商になります[1]。商a/bが表現可能な場合、式(a/b)*b + a%bはと等しくなりaます。

[1]:これはしばしば「ゼロへの切り捨て」と呼ばれます。


1

剰余演算子は残りを与えます。cのモジュラス演算子は通常、分子の符号をとります

  1. x = 5%(-3)-ここでは分子は正なので2になります
  2. y =(-5)%(3)-ここで分子は負なので、結果は-2
  3. z =(-5)%(-3)-ここで分子は負なので、結果は-2

また、modulus(remainder)演算子は整数型でのみ使用でき、浮動小数点では使用できません。


1
外部リソースへのリンクを使用してこれをバックアップできると便利です。
J ... S

1

考える方が便利だと思います mod抽象算術で定義されているので。演算としてではなく、異なる要素と異なる演算子を持つ、まったく異なるクラスの算術として。つまり、mod 3は「通常の」追加と同じはありません。あれは; 整数加算。

だからあなたがするとき:

5 % -3

整数 5をのセットの要素にマップしようとしていますmod -3。これらはの要素ですmod -3

{ 0, -2, -1 }

そう:

0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1

なんらかの理由で30時間起きている必要があるとしましょう。その日の残り時間は何時間ですか。 30 mod -24

しかし、Cが実装しているのはでなくmod、残りです。とにかく、ネガを返すのは理にかなっているということです。

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