[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Float to Rational

Luke Galea

5/5/2005 1:53:00 AM

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


9 Answers

Jannis Harder

5/5/2005 9:12:00 AM

0

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


Mark Hubbart

5/5/2005 5:10:00 PM

0

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?

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



Florian Pflug

5/5/2005 5:13:00 PM

0

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! ;-)

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

Florian Pflug

5/5/2005 5:35:00 PM

0

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

Christoph R.

5/5/2005 6:08:00 PM

0

Florian G. Pflug schrieb:

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


Zane Dodson

5/5/2005 6:17:00 PM

0

Hello,

On Fri, May 06, 2005 at 03:07:40AM +0900, Christoph wrote:
| 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,

--
Zane Dodson


Christoph R.

5/5/2005 6:46:00 PM

0

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



Mark Hubbart

5/5/2005 9:28:00 PM

0

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



Luke Galea

5/6/2005 2:43:00 PM

0

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