Haskell div mod – Cyclical Division, Modulus Visualised

Reading Time: 8 mins
When I first came to integer division and the modulus operation in Haskell, I was thrown a bit of a curve ball, but a good curve ball at that. While Haskell does have two forms of integer division, one of those is a lot different from how I was used to thinking about integer division in other languages like Java and Scala. This actually comes in very handy for algorithms that cycle through ranges of values either left to right or right to left. However, as I was more used to the way it is done in Java and Scala, it took a bit of a mind-shift when I came to Haskell and saw another way to handle integer division and modulus.

One way of doing integer division in Haskell is with the quot and rem functions. They can be put together with the function quotRem and giving a tuple of the result and the remainder and this works like many would be used to from Java or some other languages.

ghci> let x = 8 :: Integer
ghci> let y = 3 :: Integer
ghci> quotRem x y
(2,2)
ghci>
ghci> let x = (-8) :: Integer
ghci> let y = 3 :: Integer
ghci> quotRem x y
(-2,-2)
ghci> let x = (-8) :: Integer
ghci> let y = (-3) :: Integer
ghci> quotRem x y
(2,-2)

ghci> let y = -3 :: Integer
ghci> quotRem x y
(-2,2)
ghci>

I’ve put together  a visualisation and a set of rules that I use to reason about another way that Haskell provides for integer division and along with its modulus operation. First, lets take 8 div 3 and show the results of the different combinations of positive and negative numerators and denominators.
Haskell has a handy function called divMod which returns a tuple with the first element being the integer division result and the second being the remainder (the result of the mod operation).

8 div 3

ghci> let x = 8 :: Integer
ghci> let y = 3 :: Integer
ghci> divMod x y
(2,2)
ghci>

-8 div 3

ghci> let x = (-8) :: Integer
ghci> let y = 3 :: Integer
ghci> divMod x y
(-3,1)
ghci>

-8 div -3

ghci> let x = (-8) :: Integer 
ghci> let y = (-3) :: Integer
ghci> divMod x y 
(2,-2)
ghci>  

8 div -3

ghci> let x = 8 :: Integer
ghci> let y = (-3) :: Integer
ghci> divMod x y
(-3,-1)
ghci>

These can be surprising results especially when compared to, say, Scala. For example, if we take -8 mod 3 in Scala, we get -2 unlike the 1 modulus result we got in our Haskell result (-3,1)

scala> -8 % 3
res2: Int = -2

Visualising Cyclical Integer Division And Modulus

The visualisation works as follows:

  • We have a denominator block in blue which has a length equal to the denominator.
  •  We have a denominator range that we are trying to move the block into which ranges from the denominator , inclusive, to the 0 boundary, exclusive.
  •  We move the denominator block until it partially or completely enters the denominator range.
  •  We have a Divisions accumulator which gets incremented or decremented.
  • If our block moves so that it approaches the denominator range without passing through the 0 boundary, we increment our Divisions accumulator with each move.
  •  Otherwise, the denominator block passes over the 0 boundary and we decrement our Divisions accumulator with each move.
  •  The final Divisions accumulator is the division result and the difference between the top of the denominator block and the 0 boundary gives the remainder.

8 div 2

0 Boundary

1
2
3
4
5
6
7
8

Divisions 0

0 Boundary

1
2
3
4
5
6
7
8

Divisions 1

0 Boundary

1
2
3
4
5
6
7
8

Divisions 2

0 Boundary

1
2
3
4
5
6
7
8

Divisions 3

0 Boundary

1
2
3
4
5
6
7
8

Divisions 4

Our last move brings the denominator block fully into the denominator range so we have no remainder.
So, 8 div 2 is 4 remainder 0.

8 div 3

0 Boundary

1
2
3
4
5
6
7
8

Divisions 0

0 Boundary

1
2
3
4
5
6
7
8

Divisions 1

0 Boundary

1
2
3
4
5
6
7
8

Divisions 2

Our last move brings the denominator partially into the denominator range. Our Divisions accumulator is 3 and the difference between the top of the denominator block and the 0 boundary is 2.
So, 8 div 3 is 2 remainder 2.

Changing division direction -8 div 3

Our Divisions accumulator gets decremented this time because we will be crossing the 0 boundary.

-8
-7
-6
-5
-4
-3
-2
-1

0 Boundary

1
2
3

Divisions 0

-8
-7
-6
-5
-4
-3
-2
-1

0 Boundary

1
2
3

Divisions -1

-8
-7
-6
-5
-4
-3
-2
-1

0 Boundary

1
2
3

Divisions -2

-8
-7
-6
-5
-4
-3
-2
-1

0 Boundary

1
2
3

Divisions -3

Our last move brings the denominator partially into the denominator range. Our Divisions accumulator is -3 and the difference between the top of the denominator block and the 0 boundary is 1.
So, -8 div 3 is -3 remainder 1.

Making the denominator negative, -8 div -3

We can visualise the integer division in the same way. Except, this time the denominator range that we move the denominator block into is -3, inclusive to -1, inclusive.

-8
-7
-6
-5
-4
-3
-2
-1

0 Boundary

1
2
3

Divisions 1

-8
-7
-6
-5
-4
-3
-2
-1

0 Boundary

1
2
3

Divisions 2

Our last move brings the denominator partially into the denominator range. Our Divisions accumulator is 2 and the difference between the top of the denominator block and the 0 boundary is -2.
So, -8 div -3 is 2 remainder -2.

8 div -3

-3
-2
-1
1
2
3
4
5
6
7
8

Divisions 0

0 Boundary


-3
-2
-1
1
2
3
4
5
6
7
8

Divisions -1

0 Boundary

-3
-2
-1
1
2
3
4
5
6
7
8

Divisions -2

0 Boundary

-3
-2
-1
1
2
3
4
5
6
7
8

Divisions -3

0 Boundary

Our last move brings the denominator partially into the denominator range. Our Divisions accumulator is -3 and the difference between the top of the denominator block and the 0 boundary is -1.
So, 8 div -3 is -3 remainder -1

Conclusion

So integer division, when treated like it is with div and mod in Haskell, is different to what many developers would be used to from Java or Scala. The modulus operation is really a way to restrict elements to a certain range as dictated by the denominator. This is why the denominator blocks in the above visualisation are always trying to get into the denominator range without moving past it. It also makes sense to me that, when the denominator block arrives in the denominator range, the remainder is the difference between its top and the 0 boundary.

There are a lot of ways to visualise and think about these cyclical division and modulus operations. I hope this blog may help others reason about the these operations and explore other ways of thinking about them.

Leave a Reply

Your email address will not be published. Required fields are marked *