Float to Rational

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

Luke Galea wrote:

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

···

--
Jannis Harder

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:

···

On 5/4/05, Luke Galea <lgalea@gmmsolutions.com> wrote:

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?

----
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

Jannis Harder wrote:

Luke Galea wrote:

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:

[snipped code]

That a neat way of extracting the exponent! :wink:

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.

greetings, Florian Pflug

Jannis Harder wrote:

Luke Galea wrote:

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:

Here is another one - it's probably much slower, but works
better for corner-cases like (0.2).to_r

class Numeric
   def to_r
     chain_fractions.reverse.inject(nil) do |r,a|
       Rational(a) + (r.nil? ? 0 : (Rational(1) / r))
     end
   end
end

class Integer
   def chain_fractions
     [self]
   end
end

class Float
   def chain_fractions
     remainder = self
     if ! block_given? then
       coefficients = Array::new
     end
     while (true) do
       if block_given? then
         yield remainder.floor
       else
         coefficients << remainder.floor
       end
       break if remainder == remainder.floor
       remainder = 1 / (remainder - remainder.floor)
     end
     coefficients
   end
end

greetings, Florian Pflug

Florian G. Pflug schrieb:

That a neat way of extracting the exponent! :wink:

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

Hello,

Florian G. Pflug schrieb:

<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,

···

On Fri, May 06, 2005 at 03:07:40AM +0900, Christoph wrote:

--
Zane Dodson

Zane Dodson schrieb:

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

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

···

On 5/5/05, Christoph <chr_mail@gmx.net> wrote:

Zane Dodson schrieb:

>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 ..

----
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

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?

···

On Thursday 05 May 2005 17:27, Mark Hubbart wrote:

On 5/5/05, Christoph <chr_mail@gmx.net> wrote:
> Zane Dodson schrieb:
> >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 ..

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