4

I want to understand how the modulus operator works when applied to two intervals. Adding, subtracting and multiplying two intervals is trivial to implement in code, but how do you do it for modulus?

I'd be happy if someone can show me the formula, sample code or a link which explains how it works.

Background info: You have two integers x_lo < x < x_hi and y_lo < y < y_hi. What is the the lower and upper bound for mod(x, y)?

Edit: I'm unsure if it is possible to come up with the minimal bounds in an efficient manner (without calculating the mod for all x or for all y). If so, then I'll accept an accurate but non-optimal answer for the bounds. Obviously, [-inf,+inf] is a correct answer then :) but I want a bound that is more limited in size.

Björn Lindqvist
  • 19,221
  • 20
  • 87
  • 122
  • 1
    how is the modulus of _one_ interval defined? –  Jul 03 '15 at 14:25
  • It isn't. The operation I'm after is modulus of **two** intervals just like you can have division of two intervals. – Björn Lindqvist Jul 04 '15 at 16:16
  • If y was fixed, one could figure out the range by looking at mod(x_lo, y) and mod(x_hi, y). But as y changes, there is no simple pattern in the mod values. I think there isn't much to do here other than to compute mod(x_lo, y) and mod(x_hi, y) for each y, and take the union of intervals that they bound. –  Jul 06 '15 at 00:44

2 Answers2

4

It turns out, this is an interesting problem. The assumption I make is that for integer intervals, modulo is defined with respect to truncated division (round towards 0).

As a consequence, mod(-a,m) == -mod(a,m) for all a, m. Moreover, sign(mod(a,m)) == sign(a).

Definitions, before we start

Closed interval from a to b: [a,b]
Empty interval: [] := [+Inf,-Inf]
Negation: -[a,b] := [-b,-a]
Union: [a,b] u [c,d] := [min(a,c),max(b,d)]
Absolute value: |m| := max(m,-m)

Simpler Case: Fixed modulus m

It is easier to start with a fixed m. We will later generalize this to the modulo of two intervals. The definition builds up recursively. It should be no problem to implement this in your favorite programming language. Pseudocode:

def mod1([a,b], m):
    // (1): empty interval
    if a > b || m == 0:
        return []
    // (2): compute modulo with positive interval and negate
    else if b < 0:
        return -mod1([-b,-a], m)
    // (3): split into negative and non-negative interval, compute and join 
    else if a < 0:
        return mod1([a,-1], m) u mod1([0,b], m)
    // (4): there is no k > 0 such that a < k*m <= b
    else if b-a < |m| && a % m <= b % m:
        return [a % m, b % m]
    // (5): we can't do better than that
    else
        return [0,|m|-1]

Up to this point, we can't do better than that. The resulting interval in (5) might be an over-approximation, but it is the best we can get. If we were allowed to return a set of intervals, we could be more precise.

General case

The same ideas apply to the case where our modulus is an interval itself. Here we go:

def mod2([a,b], [m,n]):
    // (1): empty interval
    if a > b || m > n:
        return []
    // (2): compute modulo with positive interval and negate
    else if b < 0:
        return -mod2([-b,-a], [m,n])
    // (3): split into negative and non-negative interval, compute, and join 
    else if a < 0:
        return mod2([a,-1], [m,n]) u mod2([0,b], [m,n])
    // (4): use the simpler function from before
    else if m == n:
        return mod1([a,b], m)
    // (5): use only non-negative m and n
    else if n <= 0:
        return mod2([a,b], [-n,-m])
    // (6): similar to (5), make modulus non-negative
    else if m <= 0:
        return mod2([a,b], [1, max(-m,n)])
    // (7): compare to (4) in mod1, check b-a < |modulus|
    else if b-a >= n:
        return [0,n-1]
    // (8): similar to (7), split interval, compute, and join
    else if b-a >= m:
        return [0, b-a-1] u mod2([a,b], [b-a+1,n])
    // (9): modulo has no effect
    else if m > b:
        return [a,b]
    // (10): there is some overlapping of [a,b] and [n,m]
    else if n > b:
        return [0,b]
    // (11): either compute all possibilities and join, or be imprecise
    else:
        return [0,n-1] // imprecise

Have fun! :)

0

Let see mod(x, y) = mod. In general 0 <= mod <= y. So it's always true: y_lo < mod < y_hi

But we can see some specific cases below:

- if: x_hi < y_lo then div(x, y) = 0, then x_low < mod < x_hi
- if: x_low > y_hi then div(x, y) > 0, then y_low < mod < y_hi
- if: x_low < y_low < y_hi < x_hi, then y_low < mod < y_hi
- if: x_low < y_low < x_hi < y_hi, then y_low < mod < x_hi
- if: y_low < x_low < y_hi < x_hi, then y_low < mod < y_hi

....

  • You are thinking of the similar remainder operation which is not defined for negative numbers. But in `mod([2,2], [-3,-3]) = [2,2]` then `y_lo < mod < y_hi` is not true. – Björn Lindqvist Jul 06 '15 at 14:33
  • 1
    y € [2,4], x € [2,4] -> Take x = 3, y = 3, result: 0. Looking at your statement above and filling in the values gives 1 < 0 < 1 ... which is obviously not true. – Daniel Jour Jul 13 '15 at 07:40