Programming Praxis – Rational Numbers

In today’s Programming Praxis exercise, our goal is to implement common operations on rational numbers. Let’s get started, shall we?

We could of course just use the Data.Ratio module, which does everything we need, but that kind of defeats the purpose of the exercise.

Creating a rational number isn’t too difficult, but if you condense the various cases into a single expression like I did here you need to take care to get the signs right. Fortunately, that’s what unit testing is for. I’m using plain tuples here for the sake of brevity. In production it would be advisable to use a data type instead to prevent passing in invalid rationals such as (1,0).

ratio :: Integral a => a -> a -> (a, a)
ratio _ 0 = error "Division by zero"
ratio n d = (signum d * div n g, abs $ div d g) where g = gcd n d

For adding we use the given formula. Subtracting is the same as adding the negative of the second number.

plus :: Integral a => (a, a) -> (a, a) -> (a, a)
plus (nx, dx) (ny, dy) = ratio (nx * dy + dx * ny) (dx * dy)

minus :: Integral a => (a, a) -> (a, a) -> (a, a)
minus x (ny, dy) = plus x (-ny, dy)

Multiplication didn’t pass the unit tests at first. Turns out the formula in the Scheme solution is wrong, so I replaced it with the correct one. Division is just multiplying by the inverse of the second number.

times :: Integral a => (a, a) -> (a, a) -> (a, a)
times (nx, dx) (ny, dy) = ratio (nx * ny) (dx * dy)

divide :: Integral a => (a, a) -> (a, a) -> (a, a)
divide x (ny, dy) = times x (dy, ny)

Finally, there’s the comparison operator.

lessThan :: Integral a => (a, a) -> (a, a) -> Bool
lessThan (nx, dx) (ny, dy) = nx * dy < dx * ny

I used a decent number of unit tests to cover all of the potentially problematic cases.

main :: IO ()
main = do print $ ratio 1 2           == (1,2)
          print $ ratio 1 (-2)        == (-1,2)
          print $ ratio (-1) 2        == (-1,2)
          print $ ratio (-1) (-2)     == (1,2)
          print $ ratio 2 4           == (1,2)
          print $ plus (1,2) (-1,6)   == (1,3)
          print $ minus (1,2) (1,6)   == (1,3)
          print $ times (3,5) (5,3)   == (1,1)
          print $ times (2,5) (3,7)   == (6,35)
          print $ times (2,5) (-3,7)  == (-6,35)
          print $ divide (3,4) (3,2)  == (1,2)
          print $ divide (1,3) (-2,3) == (-1,2)
          print $ lessThan (1,3) (1,2)
          print $ lessThan (-1,2) (1,6)
          print $ lessThan (-1,2) (-1,6)

Everything passes, so it looks like things are working correctly.

About these ads

Tags: , , , , , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

Join 35 other followers

%d bloggers like this: