class Quadratic

Base class.

Users must specify a square-free integer to get a concrete class (see class method .[]).

Constants

Imag
Real

Attributes

a[R]
b[R]

Public Class Methods

[](d) click to toggle source

Provides a concrete class.

@param [Integer] d @return [Class] @raise [TypeError] if d is not an integer. @raise [RangeError] if d is not square-free.

@example

Quadratic[2].ancestors.take(4)
#=> [Quadratic[2], Quadratic::Real, Quadratic, Numeric]
Quadratic[-1].ancestors.take(4)
#=> [Quadratic[-1], Quadratic::Imag, Quadratic, Numeric]
# File lib/quadratic_number.rb, line 29
def self.[](d)
        # return a memoized subclass if exists
        return @@classes[d] if @@classes[d]

        unless d.kind_of?(Integer)
                raise TypeError, 'not an integer'
        end

        if d == 0 || d == 1 ||
           Prime.prime_division(d).any? { |p,k| k > 1 }
                raise RangeError, 'd must be square-free other than 0 or 1'
        end

        # memoize a new subclass and return it
        base = (d >= 0) ? Real : Imag
        @@classes[d] = Class.new(base) do
                # In this scope, `self` indicates a concrete subclass.
                self.const_set(:D, d)

                class << self
                        def name
                                "Quadratic[#{self::D}]"
                        end
                        alias to_s name
                        alias inspect name

                        public :new
                end
        end
end
inspect()
Alias for: name
name() click to toggle source
# File lib/quadratic_number.rb, line 49
def name
        "Quadratic[#{self::D}]"
end
Also aliased as: to_s, inspect
to_s()
Alias for: name

Private Class Methods

new(a, b = 0) click to toggle source

Returns (a+b√d).

@example

phi   = Quadratic[5].new(1, 1) / 2     #=> ((1/2)+(1/2)*√5)
omega = Quadratic[-3].new(-1, 1) / 2   #=> ((-1/2)+(1/2)*√-3)
# File lib/quadratic_number.rb, line 73
def initialize(a, b = 0)
        unless [a, b].all? { |x| __rational__(x) }
                raise TypeError, "not a rational"
        end
        @a = a
        @b = b
end

Public Instance Methods

*(other) click to toggle source

Performs multiplication.

@param [Numeric] other @return [Quadratic]

# File lib/quadratic_number.rb, line 159
def *(other)
        my_class = self.class
        if other.kind_of?(my_class)
                _a = other.a
                _b = other.b
                my_class.new(@a * _a + @b * _b * my_class::D, @a * _b + @b * _a)
        elsif __rational__(other)
                my_class.new(@a * other, @b * other)
        else
                __coerce_exec__(:*, other)
        end
end
**(index) click to toggle source

Performs exponentiation.

@param [Numeric] index @return [Quadratic/Float/Complex]

# File lib/quadratic_number.rb, line 216
def **(index)
        unless index.kind_of?(Numeric)
                num1, num2 = index.coerce(self)
                return num1 ** num2
        end

        # return 1 if index is exactly zero
        begin
                1 / index
        rescue ZeroDivisionError
                return self.class.new(1, 0)
        end

        # complex -> real
        begin
                index.to_f
        rescue
        else
                index = index.real
        end

        # quadratic -> rational or integer / float or complex
        if index.kind_of?(Quadratic)
                if index.b == 0
                        index = index.a
                else
                        index = index.to_builtin
                end
        end

        # rational -> integer
        if index.kind_of?(Rational) && index.denominator == 1
                index = index.numerator
        end

        if index.integer?
                # binary method
                x = (index >= 0) ? self : 1 / self
                n = index.abs

                z = self.class.new(1, 0)
                while true
                        n, i = n.divmod(2)
                        z *= x if i == 1
                        return z if n == 0
                        x *= x
                end
        else
                return self.to_builtin ** index
        end
end
+(other) click to toggle source

Performs addition.

@param [Numeric] other @return [Quadratic]

# File lib/quadratic_number.rb, line 125
def +(other)
        my_class = self.class
        if other.kind_of?(my_class)
                my_class.new(@a + other.a, @b + other.b)
        elsif __rational__(other)
                my_class.new(@a + other, @b)
        else
                __coerce_exec__(:+, other)
        end
end
-(other) click to toggle source

Performs subtraction.

@param [Numeric] other @return [Quadratic]

# File lib/quadratic_number.rb, line 142
def -(other)
        my_class = self.class
        if other.kind_of?(my_class)
                my_class.new(@a - other.a, @b - other.b)
        elsif __rational__(other)
                my_class.new(@a - other, @b)
        else
                __coerce_exec__(:-, other)
        end
end
-@() click to toggle source

Returns negation of the value.

@return [Quadratic]

# File lib/quadratic_number.rb, line 273
def -@
        self.class.new(-@a, -@b)
end
/(other)
Alias for: quo
coerce(other) click to toggle source

Performs type conversion.

@param [Numeric] other @return [[Numeric, Numeric]] other and self @raise [TypeError]

# File lib/quadratic_number.rb, line 91
def coerce(other)
        my_class = self.class
        if other.kind_of?(my_class)
                # my_class
                [other, self]
        elsif __rational__(other)
                # Integer and Rational
                [my_class.new(other, 0), self]
        elsif other.kind_of?(Quadratic)
                # Quadratic::Real and Quadratic::Imag
                if self.real? && other.real?
                        [other.to_f, self.to_f]
                else
                        [other.to_c, self.to_c]
                end
        elsif __real__(other)
                # Float and BigDecimal
                [other, self.to_builtin]
        elsif __complex__(other)
                # Complex
                [other, self.to_c]
        else
                # others
                raise TypeError,
                      "#{other.class} can't be coerced into #{self.class}"
        end
end
denominator() click to toggle source

Returns its denominator.

@return [Integer]

# File lib/quadratic_number.rb, line 339
def denominator
        ad = @a.denominator
        bd = @b.denominator
        ad.lcm(bd)
end
discriminant() click to toggle source

Returns its discriminant.

@return [Integer/Rational]

# File lib/quadratic_number.rb, line 398
def discriminant
        # trace ** 2 - 4 * norm
        @b * @b * (self.class::D * 4)
end
eql?(other) click to toggle source

Returns true if the two numbers are equal including their types.

@param [Object] other @return [Boolean]

# File lib/quadratic_number.rb, line 285
def eql?(other)
        if other.kind_of?(self.class)
                @a.eql?(other.a) && @b.eql?(other.b)
        else
                false
        end
end
fdiv(other) click to toggle source

Performs division.

@param [Numeric] other @return [Float/Complex]

# File lib/quadratic_number.rb, line 199
def fdiv(other)
        if other.kind_of?(Quadratic)
                self.to_builtin.fdiv(other.to_builtin)
        elsif other.kind_of?(Numeric)
                self.to_builtin.fdiv(other)
        else
                n1, n2 = other.coerce(self)
                n1.fdiv(other)
        end
end
hash() click to toggle source

Returns a hash value.

@return [Integer]

# File lib/quadratic_number.rb, line 298
def hash
        [@a, @b, self.class::D].hash
end
inspect() click to toggle source

Returns a string.

@return [String]

# File lib/quadratic_number.rb, line 327
def inspect
        '(' << __format__(:inspect) << ')'
end
Also aliased as: to_s
norm() click to toggle source

Returns its norm.

@return [Integer/Rational]

# File lib/quadratic_number.rb, line 386
def norm
        # self * self.qconj
        @a * @a - @b * @b * self.class::D
end
Also aliased as: qabs2
numerator() click to toggle source

Returns its numerator.

@return [Quadratic]

# File lib/quadratic_number.rb, line 350
def numerator
        an = @a.numerator
        ad = @a.denominator
        bn = @b.numerator
        bd = @b.denominator
        abd = ad.lcm(bd)
        self.class.new(an * (abd / ad), bn * (abd / bd))
end
qabs2()
Also aliased as: quadratic_abs2
Alias for: norm
qconj() click to toggle source

Returns its quadratic conjugate.

@return [Quadratic]

# File lib/quadratic_number.rb, line 366
def qconj
        self.class.new(@a, -@b)
end
Also aliased as: quadratic_conjugate
quadratic_abs2()
Alias for: qabs2
quadratic_conjugate()
Alias for: qconj
quo(other) click to toggle source

Performs division.

@param [Numeric] other @return [Quadratic]

# File lib/quadratic_number.rb, line 178
def quo(other)
        my_class = self.class
        if other.kind_of?(my_class)
                _a = other.a
                _b = other.b
                d = _a * _a - _b * _b * my_class::D
                self * my_class.new(_a.quo(d), -_b.quo(d))
        elsif __rational__(other)
                my_class.new(@a.quo(other), @b.quo(other))
        else
                __coerce_exec__(:quo, other)
        end
end
Also aliased as: /
to_s()
Alias for: inspect
trace() click to toggle source

Returns its trace.

@return [Integer/Rational]

# File lib/quadratic_number.rb, line 376
def trace
        # self + self.qconj
        @a * 2
end

Protected Instance Methods

to_builtin() click to toggle source

Convert to a bilt-in class' object.

@return [Float/Complex]

# File lib/quadratic_number.rb, line 317
def to_builtin
        real? ? to_f : to_c
end

Private Instance Methods

__coerce_exec__(op, other) click to toggle source
# File lib/quadratic_number.rb, line 421
def __coerce_exec__(op, other)
        if other.kind_of?(Quadratic)
                if self.real? && other.real?
                        n1 = self.to_f
                        n2 = other.to_f
                else
                        n1 = self.to_c
                        n2 = other.to_c
                end
        elsif __real__(other)
                n1 = self.to_builtin
                n2 = other
        elsif __complex__(other)
                n1 = self.to_c
                n2 = other
        else
                n1, n2 = other.coerce(self)
        end

        n1.send(op, n2)
end
__complex__(x) click to toggle source

Complex and Quadratic::Imag

# File lib/quadratic_number.rb, line 417
def __complex__(x)
        x.kind_of?(Complex) || x.kind_of?(Imag)
end
__format__(sym) click to toggle source
# File lib/quadratic_number.rb, line 443
def __format__(sym)
        str = ''
        str << @a.send(sym)
        if @b >= 0
                str << '+' << @b.send(sym)
        else
                str << '-' << (-@b).send(sym)
        end
        str << "*" if /\D\z/ === str
        str << "\u221a#{self.class::D}"
end
__rational__(x) click to toggle source

Integer and Rational

# File lib/quadratic_number.rb, line 406
def __rational__(x)
        x.kind_of?(Integer) || x.kind_of?(Rational)
end
__real__(x) click to toggle source

Integer, Rational, Quadratic::Real, Float, and BigDecimal

# File lib/quadratic_number.rb, line 412
def __real__(x)
        x.kind_of?(Numeric) && x.real?
end