81

Let a, b and c be non-large positive integers. Does a/b/c always equal a/(b * c) with C# integer arithmetic? For me, in C# it looks like:

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

So my question is: does x1 == x2 for all a, b and c?

Jason Crease
  • 1,896
  • 17
  • 17
  • 3
    This is a maths question, not a programming one. Can you explain what the programming specific part of this question is? – Oded May 30 '13 at 13:43
  • 38
    @Oded in the scope of any rational number, sure, but this is specifically referring to integer arithmetic (in C#). IMO that makes it programming-related. Maybe the rule that a/b/c == a/(b*c) holds in integer arithmetic, maybe it only holds in rational number arithmetic. – Tim S. May 30 '13 at 13:46
  • 43
    This is a perfectly reasonable question about C#, and easy to answer. – Eric Lippert May 30 '13 at 13:46
  • 12
    @Oded This is a question about computer arithmetic and whether it behaves the same as pure math. It should not be closed. – Jeffrey Sax May 30 '13 at 13:49
  • 1
    Have you done any investigation yourself regarding this? – default May 30 '13 at 13:55
  • 4
    I'd be quite interested in a mathematical proof of why (or indeed whether), ignoring overflows, the two are in fact equivalent, but I've not managed to put one together yet. – Rawling May 30 '13 at 14:06
  • @Rawling Well, given the range of an int it wouldn't be too terribly difficult to just brute force the whole thing and actually experimentially verify if every combination either is equal or overflows. – Servy May 30 '13 at 14:08
  • I've brute forced this for all values from `1` to `10000` for `a`, `b` and `c` and there was no inequality. I also tested by assigning `x1` in two steps: `x1 = a / b; x1 /= c;`, and that made no difference. – John Willemse May 30 '13 at 14:17
  • 1
    I've persuaded myself that, mathematically, `floor(a/(b*c)) = floor(floor(a/b)/c)`, although I can't phrase it very well. First note that `a/(b*c) = (a/b)/c`. Then if `x = a/b`, no integer multiple of `c` can possibly lie between `x` and `floor(x)` so `floor(x/c)` and `floor(floor(x)/c)` must be equal. – Rawling May 30 '13 at 14:22
  • 1
    possible duplicate of [integer division properties](http://stackoverflow.com/questions/2634546/integer-division-properties) – Bruno Martinez Jun 29 '13 at 18:36
  • 1
    @BrunoMartinez the proof overthere is really much easier to understand – phuclv Sep 22 '14 at 10:04

6 Answers6

76

I liked this question so much I made it the subject of my blog on June 4th, 2013. Thanks for the great question!


Large cases are easy to come by. For example:

a = 1073741823; 
b = 134217727;
c = 134217727;

because b * c overflows to a negative number.

I would add to that the fact that in checked arithmetic, the difference between a / (b * c) and (a / b) / c can be the difference between a program that works and a program that crashes. If the product of b and c overflows the bounds of an integer then the former will crash in a checked context.

For small positive integers, say, small enough to fit into a short, the identity should be maintained.


Timothy Shields just posted a proof; I present here an alternative proof. Assume all the numbers here are non-negative integers and none of the operations overflow.

Integer division of x / y finds the value q such that q * y + r == x, where 0 <= r < y.

So the division a / (b * c) finds the value q1 such that

q1 * b * c + r1 == a

where 0 <= r1 < b * c

the division ( a / b ) / c first finds the value qt such that

qt * b + r3 == a

and then finds the value q2 such that

q2 * c + r2 == qt

So substitute that in for qt and we get:

q2 * b * c + b * r2 + r3 == a

where 0 <= r2 < c and 0 <= r3 < b.

Two things equal to the same are equal to each other, so we have

q1 * b * c + r1 == q2 * b * c + b * r2 + r3

Suppose q1 == q2 + x for some integer x. Substitute that in and solve for x:

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

where

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

Can x be greater than zero? No. We have the inequalities:

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

So the numerator of that fraction is always smaller than b * c, so x cannot be greater than zero.

Can x be less than zero? No, by similar argument, left to the reader.

Therefore integer x is zero, and therefore q1 == q2.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
70

Let \ denote integer division (the C# / operator between two ints) and let / denote usual math division. Then, if x,y,z are positive integers and we are ignoring overflow,

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

where

a \ b = floor(a / b)

The jump from line [1] to line [2] above is explained as follows. Suppose you have two integers a and b and a fractional number f in the range [0, 1). It is straightforward to see that

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

If in line [1] you identify a = floor(x / y), f = (x / y) - floor(x / y), and b = z, then [3] implies that [1] and [2] are equal.

You can generalize this proof to negative integers (still ignoring overflow), but I'll leave that to the reader to keep the point simple.


On the issue of overflow - see Eric Lippert's answer for a good explanation! He also takes a much more rigorous approach in his blog post and answer, something you should look into if you feel I'm being too hand-wavy.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • 1
    Hah, that's what I was after :) – Rawling May 30 '13 at 14:25
  • I like your use of \ and / for this. Makes things much more clear. – Justin Morgan - On strike May 30 '13 at 15:29
  • @JustinMorgan The notation is actually used in some other programming languages (although I don't remember which ones at the moment). – Timothy Shields May 30 '13 at 15:31
  • 1
    @TimothyShields VB.net does. – Arie Xiao May 30 '13 at 16:08
  • I think the claim is true, but your proof seems to be missing a key step. It's possible I misunderstood your justification for line 2 => line 3. The way I interpreted it was `floor(x / y) - (x / y)` is small and `z >= 1` so taking the `floor` of that is 0 and we can ignore it. That doesn't actually follow since it's actually an addend within a `floor()` (i.e. consider `floor(1/2)` vs `floor(1/2 + 1/2)`). – rliu May 31 '13 at 07:36
  • @roliu I'll try to clarify it later when I'm not on my phone. – Timothy Shields May 31 '13 at 13:41
  • @roliu So what I was trying to clarify with those steps in the middle of the proof was that `floor(floor(x / y) / z) = floor((x / y) / z)`. Because the numerator is changing from `floor(x / y)` to `(x / y)` it is increasing *from an integer value* by an amount in `[0, 1)`. But since we're dividing that by an integer `z` and then flooring again, we have the equality. An example would be `floor(9/7) = floor(9.4536345/7)`. The `floor(1/2 + 1/2)` counterexample you mention doesn't fit here because *both* of your addends are fractional. We're guaranteed that one is an integer here. – Timothy Shields May 31 '13 at 14:11
  • @TimothyShields I agree with that argument, but I don't think it's argued convincingly in the way you presented it. In particular, I think that going from `floor(floor(x/y)/z) => floor(x/y/z)` is essentially what you did because the line in the middle doesn't offer any insight into why that's true (`x/y/z` could be fractional and so could `(floor(x/y) - x/y))/z`. I presented a proof below which, in my opinion, clarifies that idea. Dang, I finally checked and it's actually the same argument that Lippert gives. Oh well heh. – rliu May 31 '13 at 16:57
  • @roliu I updated my answer to (hopefully!) better explain that jump that was bothering you. :) – Timothy Shields Jun 05 '13 at 06:32
  • @TimothyShields Yep, that's the argument (except you technically need `a < b`). +1 – rliu Jun 05 '13 at 17:01
  • @roliu You don't need `a < b`. Only an example, but it should convince you: `floor(9 / 2) = 4 = floor((9 + 0.999) / 2)` - This is true because the `0.999` is strictly less than `1`. – Timothy Shields Jun 05 '13 at 17:09
  • @TimothyShields Ah, I was trying to understand why I was so confused by your original proof. It's because you really want `floor(floor(x/y)/z) = floor(floor(x/y)/z + (x/y - floor(x/y))/z)` which is true by the argument you give above. But you had `floor(x/y/z + (floor(x/y) - x/y)/z) = floor(x/y/z)` which wasn't (isn't?) clear to me. – rliu Jun 05 '13 at 17:56
  • @roliu I agree this newer version of that step is clearer. The previous one (though not incorrect) was a little convoluted. :) – Timothy Shields Jun 05 '13 at 18:05
4

If the absolute values of b and c are below about sqrt(2^31) (approx. 46 300), so that b * c will never overflow, the values will always match. If b * c overflows, then an error can be thrown in a checked context, or you can get an incorrect value in an unchecked context.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
2

Avoiding the overflow errors noticed by others, they always match.

Let's suppose that a/b=q1, which means that a=b*q1+r1, where 0<=r1<b.
Now suppose that a/b/c=q2, which means that q1=c*q2+r2, where 0<=r2<c.
This means that a=b(c*q2+r2)+r1=b*c*q2+br2+r1.
In order for a/(b*c)=a/b/c=q2, we need to have 0<=b*r2+r1<b*c.
But b*r2+r1<b*r2+b=b*(r2+1)<=b*c, as required, and the two operations match.

This doesn't work if b or c are negative, but I don't know how integer division works in that case either.

Teepeemm
  • 4,331
  • 5
  • 35
  • 58
0

I'll offer my own proof for fun. This also ignores overflow and only handles positives unfortunately, but I think the proof is clean and clear.

The goal is to show that

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

where / is normal division (throughout this proof).

We represent the quotient and remainder of a/b uniquely as a = kb + r (by that we mean that k,r are unique and also note |r| < |b|). Then we have:

(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

So our goal is just to show that k1 == k2. Well we have:

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

and thus:

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

Now observe from (2) that r1 is an integer (for k1*z is an integer by definition) and r1 < z (also by definition). Furthermore from (1) we know that r < y => r/y < 1. Now consider the sum r1 + r/y from (4). The claim is that r1 + r/y < z and this is clear from the previous claims (because 0 <= r1 < z and r1 is an integer so we have 0 <= r1 <= z-1. Therefore 0 <= r1 + r/y < z). Thus r1 + r/y = r2 by definition of r2 (otherwise there would be two remainders from x/y which contradicts the definition of remainder). Hence we have:

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

and we have our desired conclusion that k1 = k2.

The above proof should work with negatives except for a couple steps that you'd need to check an extra case(s)... but I didn't check.

rliu
  • 1,148
  • 6
  • 8
0

counter example: INT_MIN / -1 / 2

hustwcw
  • 57
  • 5