Rounding

In contrast to previous installments of the featured function series, this article will cover four functions with identical types, from the same typeclass, that do not do the same thing. They do almost the same thing, what we’re cavalierly describing as “rounding”, but not quite. The four functions are

  • round :: (RealFrac a, Integral b) => a -> b
  • truncate :: (RealFrac a, Integral b) => a -> b
  • ceiling :: (RealFrac a, Integral b) => a -> b
  • floor :: (RealFrac a, Integral b) => a -> b

They are all methods of the RealFrac class, and they are documented along with the class documentation.Documentation for the RealFrac class. They are all also in the standard Prelude. You may already be familiar with these concepts, and, if so, then the documentation that is already provided for these functions in the base documentation may suffice for you. But we found in the course of our work that there were a few subtleties that we didn’t quite grasp until we looked at multiple examples.In particular, truncate was new to us when we first encountered it in the code for the Mandelbrot artwork. We write this now in the hope that such extra documentation will be useful for others.

Each of these functions takes a fractional type of number, such as Double or Float or Rational, and returns an integral type of number, such as Integer or Int. As such, and given the nature of rounding, the results are always an approximation of the fractional number. Each of these follows a different rule for rounding.

  • round is the closest to rounding as you might have learned it in elementary school. round x returns the nearest integer to x; if x is equidistant between two integers, it returns the even one. The strategy of rounding ½ to the closest even number has a number of nicknames including “statistician’s rounding”, because it can avoid a sort of bias that may occur if halves were always rounded up.

  • truncate x rounds toward zero and therefore returns the nearest integer to x that is between zero and x.

It might be unusual to call ceiling and floor “rounding” functions, but they are similar to round and truncate in that they also return the nearest integer to some fractional number, but with different rules, and rounding is one of the roles they play.

  • ceiling x returns the least integer that is greater than or equal to x.

  • floor x returns the greatest integer that is less than or equal to x.

We’ll take round first; it does what you expect it to, so long as you remember that it returns the nearest even number when the given input is halfway between two integers.

λ> round 3.6
4

λ> round (-3.6)
-4

λ> map round [3.5, 4.5, 5.5, 6.5]
[4,4,6,6]

That is different from truncation.

λ> truncate 3.6
3

λ> truncate (-3.6)
-3

λ> map truncate [3.5, 4.5, 5.5, 6.5]
[3,4,5,6]

Since truncate always rounds toward zero, truncate x is the same as floor x for positive numbers and the same as ceiling x for negative numbers. One could define truncate in terms of floor and ceiling like this:

truncate x =
    case (compare x 0) of
        EQ -> 0
        GT -> floor x
        LT -> ceiling x

To make explicit the metaphor for “floor” and “ceiling”, we should imagine numberedThese floors are numbered with the ground floor as “0”, as many but not all parts of the world do. floors of a building.

Say you are, like the people in the picture, on floor 2. If you point upward toward the ceiling, you are pointing at floor 3. If you look down at the floor, you are seeing floor 2.

λ> map ceiling [2.1, 2.2, 2.6, 2.9]
[3,3,3,3]

λ> map floor [2.1, 2.2, 2.6, 2.9]
[2,2,2,2]

The metaphor also extends further down into the basement. Let’s take a trip down there to the damp negative floors.

We’re now standing on floor negative three. If you reach up to the ceiling you touch floor negative-2. Crouch down to the floor and that’s floor negative-3.

λ> map ceiling [-2.1, -2.2, -2.6, -2.9]
[-2,-2,-2,-2]

λ> map floor [-2.1, -2.2, -2.6, -2.9]
[-3,-3,-3,-3]

The notion of truncation does not fit into this metaphor, at least not pleasantly. There does seem to be a fitting spacial metaphor, but it is over the representations of the numbers. Imagine the numbers are written down, and now the truncator shows up for work.

Truncation is a crude but effective technique performed with a large chainsaw.

λ> map truncate [2.1, 2.2, 2.6, 2.9, -2.1, -2.2, -2.6, -2.9]
[2,2,2,2,-2,-2,-2,-2]

The positive numbers get smaller when truncated, but the negative numbers get greater! The truncator does not know why, but they are satisfied with a job well done.

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.