I've been participating in a programming contest and one of the problems' input data included a fractional number in a decimal format: 0.75 is one example.

Parsing that into Double is trivial (I can use read for that), but the loss of precision is painful. One needs to be very careful with Double comparisons (I wasn't), which seems redundant since one has Rational data type in Haskell.

When trying to use that, I've discovered that to read a Rational one has to provide a string in the following format: numerator % denominator, which I, obviously, do not have.

So, the question is:

What is the easiest way to parse a decimal representation of a fraction into Rational?

The number of external dependencies should be taken into consideration too, since I can't install additional libraries into the online judge.

share|improve this question
up vote 14 down vote accepted

The function you want is Numeric.readFloat:

Numeric Data.Ratio> fst . head $ readFloat "0.75" :: Rational
3 % 4
share|improve this answer
    
Thank you! This works. – Rotsor Aug 14 '11 at 12:58
5  
you might want to add readSigned if you want to be able to read negative numbers: fst . head $ readSigned readFloat "-3.14" :: Rational – newacct Sep 5 '11 at 6:16

How about the following (GHCi session):

> :m + Data.Ratio
> approxRational (read "0.1" :: Double) 0.01
1 % 10

Of course you have to pick your epsilon appropriately.

share|improve this answer
    
This is a good idea! I think this should be used instead of toRational in most circumstances! – Rotsor Aug 14 '11 at 13:05
    
Unfortunately, the choice of epsilon is not obvious here. For example, approxRational 0.999 0.0001 is 909 % 910, which is not what I want. The proper epsilon to use in this case is 0.000001 (precision squared?) – Rotsor Aug 14 '11 at 13:21

Perhaps you'd get extra points in the contest for implementing it yourself:

import Data.Ratio ( (%) )

readRational :: String -> Rational
readRational input = read intPart % 1 + read fracPart % (10 ^ length fracPart)
  where (intPart, fromDot) = span (/='.') input
        fracPart           = if null fromDot then "0" else tail fromDot
share|improve this answer
    
I don't think so. In such contests only the submission time and correctness matters. Nice solution still, short enough to code in an emergency. – Rotsor Sep 19 '12 at 13:58

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.