Float to Rational

L

Luke Galea

Hi All,

I need to convert a float to a fraction.. So 1.5 to 1 1/2..
The rational class would represent at least 3/2 well.. but I was surprised to
find that there is no way to easily go from float to rational..

Am I missing an easier way?

Thanks in advance
 
J

Jannis Harder

Luke said:
Hi All,

I need to convert a float to a fraction.. So 1.5 to 1 1/2..
The rational class would represent at least 3/2 well.. but I was surprised to
find that there is no way to easily go from float to rational..

Am I missing an easier way?

Thanks in advance
I wrote a small float => rational method:

class Float
def to_r
if self.nan?
return Rational(0,0) # Div by zero error
elsif self.infinite?
return Rational(self<0 ? -1 : 1,0) # Div by zero error
end
s,e,f = [self].pack("G").unpack("B*").first.unpack("AA11A52")
s = (-1)**s.to_i
e = e.to_i(2)
if e.nonzero? and e<2047
Rational(s)*
Rational(2)**(e-1023)*Rational("1#{f}".to_i(2),0x10000000000000)
elsif e.zero?
Rational(s)*
Rational(2)**(-1024)*Rational("0#{f}".to_i(2),0x10000000000000)
end
end
end
 
M

Mark Hubbart

Hi All,

I need to convert a float to a fraction.. So 1.5 to 1 1/2..
The rational class would represent at least 3/2 well.. but I was surprised to
find that there is no way to easily go from float to rational..

Am I missing an easier way?

there is no built-in way (that I know of), but here are two methods I
wrote a while back that should cover all the bases:
----
require 'mathn'

class Float
def to_r
n = 1
n *= 2 until (self*n) % 1 == 0
(self*n).to_i/n
end

def round_to_r
i, d = to_s.split /\./
i.to_i * 10**d.size + d.to_i / 10**d.size
end
end
----

#to_r directly converts the float to a rational, and includes any
intrinsic inaccuracies. This will be *exactly* equal to the original
float.

2.125.to_r
==>17/8
0.2.to_r
==>3602879701896397/18014398509481984

#round_to_r uses the displayed representation of the float to generate
a value that, while not always being the actual value of the float, is
much better for display, or if you know you want it rounded a tiny
bit.

0.2.round_to_r
==>1/5
0.23.round_to_r
==>23/100

It could deal with being a little smarter, for catching repeating
digits and the like.

cheers,
Mark
 
C

Christoph

Florian said:
That a neat way of extracting the exponent! ;-)

Be carefull, howevery - this will not always give
the expected results. Try e.g.

puts (0.2).to_r

This yields: 3602879701896397/18014398509481984
which of course is nearly 2/5, but since
0.2 has infinitly many digits when converted
to a binary representation, a rounding error occurs.


For serious use he'd probably better using "continued fractions" based
conversion algorithm (check out any googled side with this search term)
+ error term

def to_r(eps = 10**(-13))
...
end


/Christoph
 
Z

Zane Dodson

Hello,

|
<snip>
| For serious use he'd probably better using "continued fractions" based
| conversion algorithm (check out any googled side with this search term)
| + error term
|
| def to_r(eps = 10**(-13))
| ...
| end
<snip>

Knuth has a discussion of this in `Seminumerical Algorithms, The Art
of Computer Programming, vol. 2.'

In the third edition, it is in sec. 4.5.3 (pp. 356ff). See also
exercise 4.5.3.2.

Best regards,
 
C

Christoph

Zane said:
Knuth has a discussion of this in `Seminumerical Algorithms, The Art
of Computer Programming, vol. 2.'

In the third edition, it is in sec. 4.5.3 (pp. 356ff). See also
exercise 4.5.3.2
Florian's solution is of course nothing but continued fraction
- without the (relative) error term he could be into a long wait
calling #to_r unless he is very lucky ..


/Christoph
 
M

Mark Hubbart

Florian's solution is of course nothing but continued fraction
- without the (relative) error term he could be into a long wait
calling #to_r unless he is very lucky ..

Thanks to those who mentioned the "continued fractions" method. Here's
a new implementation:

----
require 'mathn'

class Numeric
def inverse
1/self
end
end

class Float

def to_r
n = 1
n *= 2 until (self*n) % 1 == 0
(self*n).to_i/n
end

def round_to_r
return self.to_i if self % 1 == 0
n = self
ops = []
count = 0
until ((n%1).round - n%1).abs < 1e-8 || count > 20 ||
n.abs == 1.0/0.0 || n == 0.0/0.0
int, dec = n.divmod 1
ops.concat [[:+, int.to_i], [:inverse]]
n = 1/dec
count += 1
end
n = n.round
ops.reverse.inject(n.round){|n, op| n.send(*op)}
end
end
----

Use Float#to_r for an exact representation of the float value, or
Float#round_to_r for an extremely close representation of it.

cheers,
Mark
 
L

Luke Galea

Thanks for all the great responses!

So.. I guess the next question is: Does everyone think this is a worthwhile
addition to the Ruby STDLib?If so, how do we go about getting it added to
Rational.rb?

Florian's solution is of course nothing but continued fraction
- without the (relative) error term he could be into a long wait
calling #to_r unless he is very lucky ..

Thanks to those who mentioned the "continued fractions" method. Here's
a new implementation:

----
require 'mathn'

class Numeric
def inverse
1/self
end
end

class Float

def to_r
n = 1
n *= 2 until (self*n) % 1 == 0
(self*n).to_i/n
end

def round_to_r
return self.to_i if self % 1 == 0
n = self
ops = []
count = 0
until ((n%1).round - n%1).abs < 1e-8 || count > 20 ||
n.abs == 1.0/0.0 || n == 0.0/0.0
int, dec = n.divmod 1
ops.concat [[:+, int.to_i], [:inverse]]
n = 1/dec
count += 1
end
n = n.round
ops.reverse.inject(n.round){|n, op| n.send(*op)}
end
end
----

Use Float#to_r for an exact representation of the float value, or
Float#round_to_r for an extremely close representation of it.

cheers,
Mark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,813
Messages
2,569,696
Members
45,480
Latest member
CrazyMember

Latest Threads

Top