On Wed, 19 Oct 2005, Mark Hubbart wrote:

> Note that the error is generated in rational.rb. Rational.rb does the
> comparison so that it can trap negative exponents and give a rational
> result:
>
> irb> [2 ** -1, 2 ** -2]
>    ==>[Rational(1, 2), Rational(1, 4)]
>
> Try not requiring rational.rb, and your code works perfectly. So this
> is actually a problem with the rational library. It appears that it
> does the comparison to gain speed when rational division isn't needed.
>
> A possible fix to this: change the Integer#rpower method definition in
> rational.rb from
>
>  def rpower (other)
>    if other >= 0
>      self.power!(other)
>    else
>      Rational.new!(self,1)**other
>    end
>  end
>
> to:
>
>  def rpower (other)
>    me, you = other.coerce(self)
>    return me**you unless me == self
>    if other >= 0
>      self.power!(other)
>    else
>      Rational.new!(self,1)**other
>    end
>  end
>
> This seems to fix your problem, while still letting Rational do its
> rational thing:
>
> p 2 ** SomeNumberLikeThing.new(2)
> p 2 ** SomeNumberLikeThing.new(-3)
> p 2 ** -3
>
> #<SomeNumberLikeThing:0x1c2e14 @value=4>
> #<SomeNumberLikeThing:0x1c2e14 @value=Rational(1, 8)>
> Rational(1, 8)
>
> There seems to be a very small performance hit for this:
>
> mark@eMac% ruby -rbenchmark -rrational
> -e'Benchmark.bm(10){|x|x.report("modified"){100000.times{|n|2**(-n)}}}'
>                user     system      total        real
> modified   23.220000   0.400000  23.620000 ( 27.584483)
> mark@eMac% ruby -rbenchmark -rrational
> -e'Benchmark.bm(10){|x|x.report("original"){100000.times{|n|2**(-n)}}}'
>                user     system      total        real
> original   22.400000   0.400000  22.800000 ( 26.598195)
>
>
> ... but these benchmarks are not necessarily conclusive.  I may be
> missing something big there.

I was aware where the error was generated (even looked at the code), but I 
didn't realize that it was not supposed to be loaded without being 
required explicitly. Actually I'm not requiring it, Rubygems is (it 
requires time.rb, which indirectly requires rational.rb). But turning off 
Rubygems is not an option, so it would be nice if rational.rb was changed 
per your suggestion above.

Another possibility is to change Rational#rpower to this:

   def rpower (other)
     if other.is_a?(Integer) && other < 0
       Rational.new!(self,1)**other
     else
       self.power!(other)
     end
   end

It takes a slightly smaller performance penalty than your proposal, but it 
makes things a lot faster when the exponent is not an integer.

[peterv / alya] ruby -rbenchmark -rrational 
-e'Benchmark.bm(10){|x|x.report("original"){100000.times{|n|2**(-n)}}}'
                 user     system      total        real
original   13.590000   0.090000  13.680000 ( 13.683114)
[peterv / alya] ruby -rbenchmark -rrational 
-e'Benchmark.bm(10){|x|x.report("modified"){100000.times{|n|2**(-n)}}}'
                 user     system      total        real
modified   13.870000   0.070000  13.940000 ( 13.952034)

[peterv / alya] ruby -rbenchmark -rrational 
-e'Benchmark.bm(10){|x|x.report("original_float"){100000.times{|n|2**(-n+0.1)}}}'
                 user     system      total        real
original_float  1.250000   0.000000   1.250000 (  1.249663)
[peterv / alya] ruby -rbenchmark -rrational 
-e'Benchmark.bm(10){|x|x.report("modified_float"){100000.times{|n|2**(-n+0.1)}}}'
                 user     system      total        real
modified_float  0.410000   0.000000   0.410000 (  0.408085)

Thanks for helping to sort this out.

Peter