11

Is it safe to replace a/(b*c) with a/b/c when using integer-division on positive integers a,b,c, or am I at risk losing information?

I did some random tests and couldn't find an example of a/(b*c) != a/b/c, so I'm pretty sure it's safe but not quite sure how to prove it.

Thank you.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • 3
    Why did you use both the Python and C tags. Are you trying to compare the behavior of the two languages? If not, I suggest you pick a single language and stick with it. It'll make for much better answers. – Christian Dean Jul 14 '17 at 23:33
  • 1
    What language are you using? – victor Jul 14 '17 at 23:34
  • Why wouldn't it be safe? If there was such a bug I imagine it would have surfaced and been fixed asap. – npit Jul 14 '17 at 23:34
  • 2
    @npit: If the rules of integer division make the expressions not actually equivalent, the replacement would be unsafe. – user2357112 Jul 14 '17 at 23:35
  • 1
    [Floor and ceiling functions#Nested divisions](https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Nested_divisions) – Rufflewind Jul 14 '17 at 23:39
  • I don't have a proof, but for all nonzero integers a,b,c between -400 and 400, the equality holds. – amalloy Jul 14 '17 at 23:40
  • No it's not safe. For example `b*c` could overflow, in which case you can get different results. – ShreevatsaR Jul 14 '17 at 23:45
  • @ShreevatsaR: Yes, that makes since, thank you. But an overflow is exactly what I was hoping to avoid when I asked this question, and if `b*c` overflows then I'm not safe "before I even started" (i.e., I'm at risk of losing information anyhow). – goodvibration Jul 14 '17 at 23:48
  • Also, the proof can be found in the [*Concrete Mathematics* citation](https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#cite_note-19). – Rufflewind Jul 14 '17 at 23:48
  • @goodvibration If you're interested I'll post that as an answer. :-) – ShreevatsaR Jul 14 '17 at 23:52
  • @ShreevatsaR: Like I said, it doesn't really answer my question of being at risk losing information, since I'm already "done for it" in the case you're describing. – goodvibration Jul 14 '17 at 23:54
  • @goodvibration If you don't have overflow then of course they're equivalent. Do you want a proof of that? – ShreevatsaR Jul 14 '17 at 23:54
  • @ShreevatsaR: Yes. I am hoping to get rid of `b*c` in order to avoid an overflow, so I would like to be sure that I don't get the wrong answer when converting to `a/b/c`. – goodvibration Jul 14 '17 at 23:56
  • @goodvibration ok, I have posted a proof of the result :-) Cheers, – ShreevatsaR Jul 15 '17 at 00:34
  • Are you useing signed or unsigned integers? Please read [ask] and provide a [mcve]. – too honest for this site Jul 15 '17 at 00:37
  • @ShreevatsaR: It does not matter, as signed integer overflow invokes undefined behaviour. – too honest for this site Jul 15 '17 at 00:38
  • @olaf how can undefined behaviour not matter? – ShreevatsaR Jul 15 '17 at 00:40
  • @ShreevatsaR Because those values where the product overflows already invoke UB, so the comparision to the division version it pointless. – too honest for this site Jul 15 '17 at 00:43
  • @Olaf when there is overflow the results can be anything so in that case the question becomes trivial to answer: it doesn't mean we can ignore that case in determining the answer. Unless you change the question to exclude that case, which may be what you're suggesting. – ShreevatsaR Jul 15 '17 at 00:51
  • @Olaf: I think what you're saying is that **whenever it's safe to write `a/(b*c)` in a program,** it is safe to replace it with `a/b/c`. If so, I agree. – ShreevatsaR Jul 15 '17 at 00:54
  • @olaf actually my above comment was wrong, as with unsigned integers there is no unsigned behaviour and yet the results may not be equal. So i still don't understand your comment :-) – ShreevatsaR Jul 15 '17 at 01:04
  • @ShreevatsaR: OP only mentions positive integers. This could mean very well signed integers with positive values only. This is irrelevant and would still invoke UB. That's why I asked for a MCVE. It would be good if people wait for clarification before providing possibly wrong/missleading answers. – too honest for this site Jul 15 '17 at 01:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149267/discussion-between-shreevatsar-and-olaf). – ShreevatsaR Jul 15 '17 at 01:28
  • @Olaf I think the question is clear enough as it is. I changed the program in my answer to work with unsigned integers. This shows that signed-integer overflow is completely irrelevant, as UB only *strengthens* the point. Discuss in the chatroom linked above, or on my answer, if you think the answer is possibly wrong/misleading. – ShreevatsaR Jul 15 '17 at 02:16
  • @Olaf: The question says "positive integers", what do you think I'm using??? – goodvibration Jul 15 '17 at 10:10
  • @goodvibration: There is no positive integer type neither in Python nor in C or most other programming languages (for the two languages you initially tagged have no positive integers. C has **unsigned** integers, which can be zero. Python only has signed integers). You did not provide a [mcve] which would have made your question clear. So the fault is on your side using unclear/imprecise terms and bad tags!!11!eleven!! – too honest for this site Jul 15 '17 at 14:39
  • [Is (a/b)/c equal to a/(b*c) for integer division?](https://math.stackexchange.com/q/2557458/90333). Another duplicate: [integer division properties](https://stackoverflow.com/q/2634546/995714) – phuclv Apr 17 '19 at 16:13

3 Answers3

12

Mathematics

As mathematical expressions, ⌊a/(bc)⌋ and ⌊⌊a/b⌋/c⌋ are equivalent whenever b is nonzero and c is a positive integer (and in particular for positive integers a, b, c). The standard reference for these sorts of things is the delightful book Concrete Mathematics: A Foundation for Computer Science by Graham, Knuth and Patashnik. In it, Chapter 3 is mostly on floors and ceilings, and this is proved on page 71 as a part of a far more general result:

screenshot from book

In the 3.10 above, you can define x = a/b (mathematical, i.e. real division), and f(x) = x/c (exact division again), and plug those into the result on the left ⌊f(x)⌋ = ⌊f(⌊x⌋)⌋ (after verifying that the conditions on f hold here) to get ⌊a/(bc)⌋ on the LHS equal to ⌊⌊a/b⌋/c⌋ on the RHS.

If we don't want to rely on a reference in a book, we can prove ⌊a/(bc)⌋ = ⌊⌊a/b⌋/c⌋ directly using their methods. Note that with x = a/b (the real number), what we're trying to prove is that ⌊x/c⌋ = ⌊⌊x⌋/c⌋. So:

  • if x is an integer, then there is nothing to prove, as x = ⌊x⌋.
  • Otherwise, ⌊x⌋ < x, so ⌊x⌋/c < x/c which means that ⌊⌊x⌋/c⌋ ≤ ⌊x/c⌋. (We want to show it's equal.) Suppose, for the sake of contradiction, that ⌊⌊x⌋/c⌋ < ⌊x/c⌋ then there must be a number y such that ⌊x⌋ < y ≤ x and y/c = ⌊x/c⌋. (As we increase a number from ⌊x⌋ to x and consider division by c, somewhere we must hit the exact value ⌊x/c⌋.) But this means that y = c*⌊x/c⌋ is an integer between ⌊x⌋ and x, which is a contradiction!

This proves the result.

Programming

#include <stdio.h>

int main() {
  unsigned int a = 142857;
  unsigned int b = 65537;
  unsigned int c = 65537;

  printf("a/(b*c) = %d\n", a/(b*c));
  printf("a/b/c = %d\n", a/b/c);
}

prints (with 32-bit integers),

a/(b*c) = 1
a/b/c = 0

(I used unsigned integers as overflow behaviour for them is well-defined, so the above output is guaranteed. With signed integers, overflow is undefined behaviour, so the program can in fact print (or do) anything, which only reinforces the point that the results can be different.)

But if you don't have overflow, then the values you get in your program are equal to their mathematical values (that is, a/(b*c) in your code is equal to the mathematical value ⌊a/(bc)⌋, and a/b/c in code is equal to the mathematical value ⌊⌊a/b⌋/c⌋), which we've proved are equal. So it is safe to replace a/(b*c) in code by a/b/c when b*c is small enough not to overflow.

ShreevatsaR
  • 38,402
  • 17
  • 102
  • 126
  • 1
    Whether integer overflow is defined or not depends on the language. The question is not tagged with a specific language after the edit, hence it cannot be assumed there even exists an unsigned type or that signed integer overflow can in fact happen, or is UB. Even with the tags an answer would have to be very different for Python and C (Python has none, but it has integers of arbitrary length and no UB). – too honest for this site Jul 15 '17 at 05:26
  • 1
    @Olaf To be clear, the answer is: (1) if there's no overflow, then the expressions are equivalent: this case is independent of language (2) if there is overflow, they are not equivalent: for this case, it doesn't whether there exists an unsigned or signed type, whether either of them results in undefined behavior, or anything like that: all that matters is that if it can happen that `b*c` is not equal to the product of the integers `b` and `c`, then the expressions are inequivalent. So the answer indeed applies to Python too, because in that it the second case (overflow) is vacuous. – ShreevatsaR Jul 15 '17 at 05:30
2

Nested floor division can be reordered as long as you keep track of your divisors and dividends.

#python3.x
x // m // n = x // (m * n)

#python2.x
x / m / n = x / (m * n)

Proof (sucks without LaTeX :( ) in python3.x:

Let k = x // m
then k - 1 < x / m <= k
and (k - 1) / n < x / (m * n) <= k / n

In addition, (x // m) // n = k // n
and because x // m <= x / m and (x // m) // n <= (x / m) // n
k // n <= x // (m * n)

Now, if k // n < x // (m * n)
then k / n < x / (m * n)
and this contradicts the above statement that x / (m * n) <= k / n

so if k // n <= x // (m * n) and k // n !< x // (m * n)
then k // n = x // (m * n)

and (x // m) // n = x // (m * n)

https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Nested_divisions

victor
  • 1,573
  • 11
  • 23
2

While b*c could overflow (in C) for the original computation, a/b/c can't overflow, so we don't need to worry about overflow for the forward replacement a/(b*c) -> a/b/c. We would need to worry about it the other way around, though.

Let x = a/b/c. Then a/b == x*c + y for some y < c, and a == (x*c + y)*b + z for some z < b.

Thus, a == x*b*c + y*b + z. y*b + z is at most b*c-1, so x*b*c <= a <= (x+1)*b*c, and a/(b*c) == x.

Thus, a/b/c == a/(b*c), and replacing a/(b*c) by a/b/c is safe.

user2357112
  • 260,549
  • 28
  • 431
  • 505