web-dev-qa-db-ja.com

C#整数演算では、a / b / cは常にa /(b * c)と等しくなりますか?

A、b、cを大きくない正の整数とします。 a/b/cは常にa /(b * c)とC#整数演算で等しいですか?私にとって、C#では次のようになります。

int a = 5126, b = 76, c = 14;
int x1 = a / b / c;
int x2 = a / (b * c);

だから私の質問は:しますかx1 == x2すべてのa、b、cについて?

81
Jason Crease

_\_は整数除算(2つのints間のC #_/_演算子)を示し、_/_は通常の数学除算を示します。次に、_x,y,z_が正の整数であり、オーバーフローを無視である場合、

_(x \ y) \ z
    = floor(floor(x / y) / z)      [1]
    = floor((x / y) / z)           [2]
    = floor(x / (y * z))
    = x \ (y * z)
_

どこ

_a \ b = floor(a / b)
_

上記の行_[1]_から行_[2]_へのジャンプは次のように説明されます。 2つの整数ab、および範囲_[0, 1)_の小数fがあるとします。それを見るのは簡単です

_floor(a / b) = floor((a + f) / b)  [3]
_

行_[1]_でa = floor(x / y)f = (x / y) - floor(x / y)、および_b = z_を識別した場合、_[3]_は_[1]_および_[2]_ は同じ。

この証明を負の整数に一般化することはできますが(それでもオーバーフローを無視)、要点を単純にするために読者に任せます。


overflowの問題について-良い説明については、EricLippertの回答を参照してください。彼はまた、 彼のブログ投稿 ではるかに厳密なアプローチを取り、答えます。私が手に負えすぎていると感じた場合は、何かを調べる必要があります。

71
Timothy Shields

私はこの質問がとても好きで、それを主題にしました 2013年6月4日の私のブログ 。素晴らしい質問をありがとう!


大きなケースは簡単に手に入ります。例えば:

_a = 1073741823; 
b = 134217727;
c = 134217727;
_

_b * c_が負の数にオーバーフローするためです。

チェックされた算術では、a / (b * c)と_(a / b) / c_の違いが違いになる可能性があるという事実を追加します動作するプログラムとクラッシュするプログラムの間。 bcの積が整数の境界を超えた場合、前者はチェックされたコンテキストでクラッシュします。

小さい正の整数、たとえば、shortに収まるほど小さい場合は、IDを維持する必要があります。


TimothyShieldsが証明を投稿しました。ここに別の証明を提示します。ここでのすべての数値は非負の整数であり、どの演算もオーバーフローしないと仮定します。

_x / y_の整数除算は、_q * y + r == x_のような値qを見つけます。ここで、_0 <= r < y_です。

したがって、除算a / (b * c)は、次のような値_q1_を見つけます。

_q1 * b * c + r1 == a
_

ここで_0 <= r1 < b * c_

除算_( a / b ) / c_は、最初に値qtを次のように見つけます。

_qt * b + r3 == a
_

次に、次のような値_q2_を見つけます。

_q2 * c + r2 == qt
_

したがって、それをqtの代わりに使用すると、次のようになります。

_q2 * b * c + b * r2 + r3 == a
_

ここで、_0 <= r2 < c_および_0 <= r3 < b_。

同じものに等しい2つのものは互いに等しいので、

_q1 * b * c + r1 == q2 * b * c + b * r2 + r3
_

ある整数xに対して_q1 == q2 + x_を想定します。それを代入してxを解きます。

_q2 * b * c + x * b * c + r1 = q2 * b * c + b * r2 + r3
x  = (b * r2 + r3 - r1) / (b * c)
_

どこ

_ 0 <= r1 < b * c
 0 <= r2 < c
 0 <= r3 < b
_

xをゼロより大きくすることはできますか?いいえ。不平等があります。

_ b * r2 + r3 - r1 <= b * r2 + r3 <= b * (c - 1) + r3 < b * (c - 1) + b == b * c
_

したがって、その分数の分子は常に_b * c_より小さいため、xをゼロより大きくすることはできません。

xをゼロ未満にすることはできますか?いいえ、同様の議論により、読者に任せました。

したがって、整数xはゼロであり、したがって_q1 == q2_です。

77
Eric Lippert

bcの絶対値がsqrt(2^31)(約46 300)を下回っていて、b * cがオーバーフローしない場合、値は常に一致。 b * cがオーバーフローした場合、checkedコンテキストでエラーがスローされるか、uncheckedコンテキストで誤った値を取得する可能性があります。

4
Tim S.

他の人が気付いたオーバーフローエラーを避けて、それらは常に一致します。

_a/b=q1_、つまり_a=b*q1+r1_、ここで_0<=r1<b_を意味するとします。
ここで、_a/b/c=q2_と仮定します。これは、_q1=c*q2+r2_を意味します。ここで、_0<=r2<c_です。
これは、a=b(c*q2+r2)+r1=b*c*q2+br2+r1を意味します。
a/(b*c)=a/b/c=q2を実行するには、_0<=b*r2+r1<b*c_が必要です。
ただし、必要に応じてb*r2+r1<b*r2+b=b*(r2+1)<=b*cを実行すると、2つの操作が一致します。

bまたはcが負の場合、これは機能しませんが、その場合にも整数除算がどのように機能するかはわかりません。

2
Teepeemm

反例:INT_MIN/-1/2

0
hustwcw

私は楽しみのために私自身の証拠を提供します。これもオーバーフローを無視し、残念ながらポジティブのみを処理しますが、証拠はクリーンで明確だと思います。

目標はそれを示すことです

floor(floor(x/y)/z) = floor(x/y/z)

ここで、/は通常の除算です(この証明全体)。

a/b一意にの商と剰余をa = kb + rとして表します(つまり、k,rは一意であり、|r| < |b|にも注意してください)。次に、次のようになります。

(1) floor(x/y) = k => x = ky + r
(2) floor(floor(x/y)/r) = k1 => floor(x/y) = k1*z + r1
(3) floor(x/y/z) = k2 => x/y = k2*z + r2

したがって、私たちの目標は、そのk1 == k2を示すことだけです。さて、私たちは持っています:

k1*z + r1 = floor(x/y) = k = (x-r)/y (from lines 1 and 2)
=> x/y - r/y = k1*z + r1 => x/y = k1*z + r1 + r/y

したがって:

(4) x/y = k1*z + r1 + r/y (from above)
x/y = k2*z + r2 (from line 3)

ここで、(2)から、r1が整数(k1*zは定義上整数)およびr1 < z(これも定義上)であることに注意してください。さらに、(1)から、r < y => r/y < 1であることがわかります。ここで、(4)の合計r1 + r/yについて考えます。主張はr1 + r/y < zであり、これは前の主張から明らかです(0 <= r1 < zr1は整数であるため、0 <= r1 <= z-1があります。したがって、0 <= r1 + r/y < z)。したがって、r1 + r/y = r2の定義によるr2(そうでない場合、x/yからtwo剰余があり、剰余の定義と矛盾します)。したがって、次のようになります。

x/y = k1*z + r2
x/y = k2*z + r2

そして、k1 = k2という望ましい結論が得られました。

上記の証明は、追加のケースをチェックする必要があるいくつかの手順を除いて、ネガで機能するはずです...しかし、私はチェックしませんでした。

0
rliu