Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the origin of "Ratio has zero denominator" Exception

As a personal excercize in the process of learning Haskell, I'm trying to port this F# snippet for Random Art.

I've not embedded full source code for not bloating the question, but is available as gist.

An important part of the program is this Expr type:


data Expr =
    VariableX
  | VariableY
  | Constant
  | Sum Expr Expr
  | Product Expr Expr
  | Mod Expr Expr
  | Well Expr
  | Tent Expr
  | Sin Expr
  | Level Expr Expr Expr
  | Mix Expr Expr Expr
  deriving Show

and two functions:

  • gen :: Int -> IO Expr random generates a tree-like structure given a number of iterations

  • eval :: Expr -> IO (Point -> Rgb Double) walks the tree and terminates producing a drawing function.

More high is the number passed to gen than higher are the probability that the following exception is generated: Ratio has zero denominator.

I'm new to Haskell so to solve the problem I've tried to compile it as above:


ghc RandomArt.hs -prof -auto-all -caf-all

Obtaining only this more (to me quite useless) info:


$ ./RandomArt +RTS -xc

*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace:
  GHC.Real.CAF
  --> evaluated by: Main.eval.\,
  called from Main.eval,
  called from Main.tga.pxs',
  called from Main.tga,
  called from Main.save,
  called from Main.main,
  called from :Main.CAF:main
  --> evaluated by: Main.eval.\.r,
  called from Main.eval.\,
  called from Main.eval,
  called from Main.tga.pxs',
  called from Main.tga,
  called from Main.save,
  called from Main.main,
  called from :Main.CAF:main
*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace:
  Main.tga,
  called from Main.save,
  called from Main.main,
  called from GHC.Real.CAF
RandomArt: Ratio has zero denominator

The code that persist the generated function to a TGA file works because it was my previous excercize (a port from OCaml).

I've tried executing various Expr tree from GHCi, assembling data by hand or applying functions as in the program but I wasn't able to identify the bug.

Haskell docs talks about a package named loch that should able to compile preserving source code line numbers, but I was not able to install it (while I normally install with cabal install every package I need).

The question, to be honest are two:

  • where's is the bug (in this specific case)?

  • which tool do I need to master to find bugs like this (or bugs in general)?

Thanks in advance.

like image 325
gsscoder Avatar asked Sep 22 '15 18:09

gsscoder


1 Answers

The exception

Let's focus on the exception first.

Finding the bug

where's is the bug (in this specific case)?

In mod'. We can check this easily if we provide an alternative version instead of the one by Data.Fixed:

mod' :: RealFrac a => a -> a -> a
mod' _ 0 = error "Used mod' on 0"
mod' a b =
    let k = floor $ a / b
    in a - (fromInteger k) * b

We now get Used mod' on 0.

Rationale

which tool do I need to master to find bugs like this (or bugs in general)?

In this case, the necessary hint was already in the exception's message:

Ratio has zero denominator

This means that there's a place where you divide by zero in the context of a Ratio. So you need to look after all places where you divide something. Since you use only (/) and mod', it boils down to whether one of them actually can throw this exception:

  • (/) usually returns ±Infinity on division by zero if used on Double,
  • mod' uses toRational internally, which is a Ratio Integer.

So there's only one culprit left. Note that the other implementation yields the same results if b isn't zero.

The actual problem

Using mod or mod' with b == 0 isn't well-defined. After all, a modulo operation should hold the following property:

prop_mod :: Integral n => n -> n -> Bool
prop_mod a b = 
  let m = a `mod` b
      d = a `div` b

  in a == b * d + m   -- (1)
     && abs m < abs b -- (2)

If b == 0, there doesn't exist any pair (d, m) such that (1) and (2) hold. If we relax this law and throw (2) away, the result of mod isn't necessarily unique anymore. This leads to the following definition:

mod' :: RealFrac a => a -> a -> a
mod' a 0 = a -- this is arbitrary
mod' a b =
    let k = floor $ a / b
    in a - (fromInteger k) * b

However, this is an arbitrary definition. You have to ask yourself, "What do I actually want to do if I cannot use mod in a sane way". Since F# apparently didn't complain about a % 0, have a look at their documentation.

Either way, you cannot use a library mod function, since they aren't defined for a zero denominator.

like image 169
Zeta Avatar answered Oct 20 '22 22:10

Zeta