A Rational number class in Ruby


As is my custom when learning a new language, I implement a basic custom Rational class in Ruby:

# implementation of a Rational number class in Ruby

module MyRational
    class Rational
        attr_reader :numerator, :denominator

        def initialize(num, denom = 1)
            if denom == 0
                raise "Denominator cannot be 0 for a rational number"
            end
            
            @numerator = num
            @denominator = denom

            normalize
        end

        
        # += etc all come for free when the basic operator is overloaded!
        def +(other)
            @numerator = @numerator * other.denominator + @denominator * other.numerator
            @denominator = @denominator * other.denominator

            normalize
            self
        end
        

        def -(other)
            @numerator = @numerator * other.denominator - @denominator * other.numerator
            @denominator = @denominator * other.denominator

            normalize
            self
        end

        
        def *(other)
            @numerator = @numerator * other.numerator
            @denominator = @denominator * other.denominator

            normalize
            self
        end

        
        def /(other)
            @numerator = @numerator * other.denominator
            @denominator = @denominator * other.numerator

            normalize
            self
        end


        def to_s
            if @denominator == 1
                @numerator.to_s
            else
                @numerator.to_s + "/" + @denominator.to_s
            end
        end

        
        # generics class methods
        def self.+(f, s)
            n = f.numerator * s.denominator + f.denominator * s.numerator
            d = f.denominator * s.denominator
            
            Rational.new(n, d)
        end

        def self.-(f, s)
            n = f.numerator * s.denominator - f.denominator * s.numerator
            d = f.denominator * s.denominator
            
            Rational.new(n, d)
        end


        def self.*(f, s)
            n = f.numerator * s.numerator
            d = f.denominator * s.denominator
            
            Rational.new(n, d)
        end


        def self./(f, s)
            n = f.numerator * s.denominator
            d = f.denominator * s.numerator
            
            Rational.new(n, d)
        end


        private

        def gcd(x, y)
            if y == 0
                x
            else
                gcd(y, x%y)
            end
        end

        
        def normalize()
            g = gcd(@numerator, @denominator)

            @numerator /= g
            @denominator /= g
        end
    end
end

Taking it for a test spin:

irb(main):003:0> load "my_rational.rb"
load "my_rational.rb"
=> true
irb(main):004:0> x = MyRational::Rational.new(1,2)
x = MyRational::Rational.new(1,2)
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2>
irb(main):005:0> y = MyRational::Rational.new(3,4)
y = MyRational::Rational.new(3,4)
=> #<MyRational::Rational:0x007fd02a0a21a0 @numerator=3, @denominator=4>
irb(main):006:0> x += y
x += y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=5, @denominator=4>
irb(main):007:0> puts x
puts x
5/4
=> nil
irb(main):008:0> x -= y
x -= y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2>
irb(main):009:0> puts x
puts x
1/2
=> nil
irb(main):010:0> x *= y
x *= y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=3, @denominator=8>
irb(main):011:0> puts x
puts x
3/8
=> nil
irb(main):012:0> x /= y
x /= y
=> #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2>
irb(main):013:0> puts x
puts x
1/2
=> nil
irb(main):014:0> MyRational::Rational.+(x, y)
MyRational::Rational.+(x, y)
=> #<MyRational::Rational:0x007fd02a0bad90 @numerator=5, @denominator=4>
irb(main):015:0> puts MyRational::Rational.+(x, y)
puts MyRational::Rational.+(x, y)
5/4
=> nil
irb(main):016:0> puts MyRational::Rational.-(x, y)
puts MyRational::Rational.-(x, y)
-1/4
=> nil
irb(main):017:0> puts MyRational::Rational.*(x, y)
puts MyRational::Rational.*(x, y)
3/8
=> nil
irb(main):018:0> puts MyRational::Rational./(x, y)
puts MyRational::Rational./(x, y)
2/3
=> nil

Of course, operations like number op rational, rational op number, where “op” is an operation will not work. These could be added by extending the number classes, etc, and also adding support in the class methods, but perhaps there is a better way that I will learn when I improve my Ruby knowledge! For now, the more I use Ruby, the more I like it!

Advertisements
A Rational number class in Ruby

2 thoughts on “A Rational number class in Ruby

  1. Nice to see you learning Ruby!

    A few fun idiomatic usages to try:
    1. Two spaces for indentation is the usual in Ruby
    2. @numerator *= other.numerator (i.e. x = x + 1 can be written x += 1 and is idiomatic Ruby)
    3. @numerator.to_s + “/” + @denominator.to_s => faster and more idiomatic to do “#{@numerator}/#{@denominator}”
    4. line 8: raise “Denominator cannot be 0 for a rational number” if denom.zero?
    5. lines 12 and 13 can also be written in one line: @numerator, @denominator = num, denom
    6. lines 99 and 105 could be structured as a guard clause/simple case, then more complex case):
    def gcd(x, y)
    return x if y.zero?
    gcd(y, x%y)
    end

    Greatly enjoying your blog posts. Cheers!

    Like

    1. Timmy Jose says:

      Thanks for the feedback, Jon! Very helpful indeed! 🙂 … and yes, your earlier tweet about Ruby kind of inspired me to take a dive. I can’t believe I’ve put it off for so long – it really is a beautiful language, and I have only myself to blame for having been deceived by the surface similarities with Python!

      Like

Speak your mind!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s