3

I want to code a function makeFraction :: Float -> Float -> (Int, Int) which returns (x,y) whenever I say makeFraction a b such that x/y is a proper fraction equivalent to a / b. For eg, makeFraction 17.69 5.51 should return (61,19).

I have a subroutine to calculate gcd of two numbers but my first task is to convert a and b to Int e.g. 17.69 and 5.51 should be converted into 1769 and 551.

Now I want to do it for numbers with arbitrary decimal places. Prelude function does not help me much. For instance, when I say toFraction(0.2); it returns 3602879701896397 % 18014398509481984 which would severely strain the correctness of my later computations.

Later I tried getting fractional values by using another library function properFraction(17.69) which suppose to give me only 0.69 but it produces 0.69000...013 which is not I would accept in a proper state of mind.

It does look like a problem arising from Floating point arithmatic. Till now I am not doing any data manipulation but only asking for the part of stored bits which I should be able to fetch from processor registers/memory location. Is there any special function library in Haskell to do such tasks?

PS: Seems like some useful tips are here How to parse a decimal fraction into Rational in Haskell? . But since I have typed so much, I would like to post it. At least the context is different here.

Community
  • 1
  • 1
Dilawar
  • 5,438
  • 9
  • 45
  • 58

4 Answers4

8

Yes, it is the limited precision of floating-point arithmetic you're encountering. The floating-point format cannot represent 0.2 exactly, so toFraction is actually giving you the exact rational value of the Float number you get when you ask for 0.2.

Similarly, 17.69 cannot be represented exactly, and because the point floats, its best representation has a larger absolute error than the error in the representation of 0.69. Thus, when you take away the integer part, the resulting bits are not the same as if you had asked to represent 0.69 as good as possible from the beginning, and this difference can be seen when the implementation prints out the result in decimal form.

hmakholm left over Monica
  • 23,074
  • 3
  • 51
  • 73
4

It seems to me that instead of using a floating-point type like Float or Double, you should do all your computations using a type that can represent those numbers exactly, like Rational. For example,

(17.69 :: Rational) / (5.51 :: Rational)

evaluates to 61 % 19

newacct
  • 119,665
  • 29
  • 163
  • 224
  • This tip is really nice. Unfortunately type of function is given to me and I am not allowed to change it. Now the problem is 'can I recover the fraction from the floating point representation?' using some function. – Dilawar Sep 05 '11 at 07:05
  • 5
    @Dilawar: there is a function `Ratio.approxRational` which will return the simplest fraction within a certain radius of your number – newacct Sep 05 '11 at 10:07
1

As mentioned in the other answers, a Float cannot necessarily represent a given decimal number exactly. In particular, a Float is stored internally using the form a/(2^m). As a result, real numbers like 3/10 can only ever be approximated by floating point numbers.

But if a decent approximation is all you need, this might help:

import Data.Ratio

convertFloat :: Float -> Rational
convertFloat f = let
        denom = 10^6
        num = fromInteger denom * f
        in round num % denom

For example:

> convertFloat 17.69
1769 % 100
> convertFloat 17.69 / convertFloat 5.51
61 % 19
Judah Jacobson
  • 543
  • 4
  • 10
1

Check out base's Numeric module, especially the floatToDigits function.

> floatToDigits 10 17.69
([1,7,6,9],2)
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380