Roots in Ruby

4
n x x 1/n (x+iy)= ae i*arg = a*[cos(arg)+i *sin(arg)] a = p x 2 + y 2 arg = tan -1 y/x π root n =(x + iy) 1/n root n =(ae i*arg ) 1/n root n,k =(ae i*(arg+2πk) ) 1/n root n,k =(a) 1/n (e i*(arg+2πk) ) 1/n root n,k = a 1/n e i*(arg+2πk)/n root n,k = a 1/n [cos(arg/n +2πk/n)+ i * sin(arg/n +2πk/n)] mag = a 1/n theta = arg/n, delta =2π/n root n,k = mag*[cos(theta + k * delta)+ i * sin(theta + k * delta)] k =0 r = mag n =3 8e i*π/6 r = mag =2 -8 (-2)

description

Extend the Ruby programming language with two methods, root and roots, to easily find all the real and complex roots of real and complex numbers.

Transcript of Roots in Ruby

Page 1: Roots in Ruby

Roots in Ruby

Jabari Zakiya

May 17, 2011

Introduction

The nth root of a number x is mathematically expressed as n√x or x1/n. In Ruby these operations equate to

x**(1.0/n) or x**(n**-1), for x real or complex. Now for every root n of x there are n distinct root values, but theseoperations will only return one default value. So how do you �nd the rest of them? I show here how to extendRuby to easily �nd all the roots of real and complex numbers with two simple to use methods, root and roots.

Mathematical Foundations

Complex numbers have the form: (x+iy) = aei∗arg = a∗[cos(arg)+i∗sin(arg)], where the magnitude a =√x2 + y2

and angle arg = tan−1y/x. So a real number can be treated as a complex number whose imaginary part is 0, wherethe arg of a positive real is 0, and the arg of a negative real is π radians (180 degrees ccw).

Therefore, to �nd the n root values of a number we can do:

1) rootn = (x+ iy)1/n

2) rootn = (aei∗arg)1/n

3) rootn,k = (aei∗(arg+2πk))1/n

4) rootn,k = (a)1/n(ei∗(arg+2πk))1/n

5) rootn,k = a1/nei∗(arg+2πk)/n

6) rootn,k = a1/n[cos(arg/n+ 2πk/n) + i ∗ sin(arg/n+ 2πk/n)]

and de�ning mag = a1/n, theta = arg/n, and delta = 2π/n, then

7) rootn,k = mag∗[cos(theta+ k ∗ delta) + i ∗ sin(theta+ k ∗ delta)], for k=0..n-1This shows that for a root n there are n distinct root values, starting at initial angle theta (k = 0), evenly spaced

delta radians (360/n degrees) apart counter clockwise (ccw) around a circle with r = mag in the complex plane.We now have all the mathemtical pieces to code to �nd the n distinct root values.Figure 1 shows the graphical depiction of the cubed root (n = 3) values the root method returns for the real

numbers 8 and -8, and complex 8ei∗π/6 (directly coded in Ruby as 8*E**(Complex::I*Math::PI/6)). They aremapped onto a circle of r = mag = 2, where theta is 1/3 of arg for each number, and delta is 120 degrees.

Figure 1:

Notice that the 'calculator' (default) cube root for 8 is its �rst root, but for−8 it's its second ccw root (−2). Infact, for an odd numbered root of a negative real its 'calculator' root will always be the middle numbered root (onthe negative real axis). For complex numbers, the �rst root will be its default root.

1

Page 2: Roots in Ruby

Coding Roots in Ruby

Instead of doing x**(1.0/n), or x**(n**-1), to �nd the default root of a number I want to do x.root(n) (calculatormode), or do x.root(n,k) to �nd the kth root of n. I want to use natural numbering, so the �rst root is k=1, thesecond k=2, etc. I want roots to do just what it sounds like it should do, which is return a collection (an array) ofroot values. The default x.roots(n) will return all n roots. When using options, x.roots(n,opt) will return a subsetof roots for the options: 'e|E' for even roots; 'o|O' for odd roots; 'i|I' for imaginary roots; 'r|R| for real roots; and'c|C' for complex roots.

To start coding, since 7) computes all the roots of a number let's create a function called rootn to do it, whichwill be the workhorse function inside of root and roots. It takes 4 parameters and returns the results of the lastoperation. It uses the Complex(x,y) function from the Ruby standard lib Complex, and the cos|sin functions fromthe Ruby Math lib. A rudimentary implementation of rootn is shown in Listing 1.

Listing 1

def rootn(mag,theta,delta,k)

angle_n = theta + k*delta

mag*Complex(Math::cos(angle_n),Math::sin(angle_n))

end

Because Ruby is compiled from C it uses the C math library, which gives incorrect values for cos|sin on the x|yaxes e.g. cos(π/2) = 6.12303176911189e-17; sin(π) = 1.22460635382238e-16; cos(3π/2) = -1.83690953073357e-16;and sin(2π) = -2.44921970764475e-16. These all should be 0.0, which causes incorrect root values at these angles.I '�x' these errors by creating alias sin|cos functions to use in rootn which set them to 0.0 if the absolute value ofthe other function is 1.0 (on an axis). This is a �x to this problem which gives correct results for all root valuesthat fall on the x|y axes for all cpu sizes.

Listing 2

def sine(x); Math::cos(x).abs == 1 ? 0 : Math:sin(x) end

def cosine(x); Math::sin(x).abs == 1 ? 0 : Math:cos(x) end

def rootn(mag,theta,delta=0,k=0)

angle_n = theta + k*delta

mag*Complex(cosine(angle_n),sine(angle_n))

end

The code for the parameters mag, delta, and theta is simple and straightforward.In Ruby a number's absolute (magnitude) value |a| is x.abs, its arg x.arg, and π is Math::PI, so:

mag = self.abs**n**-1 ; theta = self.arg/n ; delta = 2*Math::PI/n

where self is the number object that root|s operates on. In Ruby, writung self can be eliminated when itsobject is implicit, so in the listings I use the abreviated form where applicable.

To have root provide the same default behavior as standard Ruby a few speci�c conditions of real numbers haveto be taken into consideration. When taking the root of a postive real standard Ruby will return the positive realvalue root, i.e. the mag of the number. If the number is negative and the root is odd, Ruby returns the -mag, i.e.the default cube root of -27 is -3 (which is actually -27.root 3,2). The default even root of a negative real in Rubyis the �rst ccw root. The code for root takes these design criteria into consideration.

Listing 3

def root(n,k=0)

mag = abs**n**-1 ; theta = arg/n ; delta = 2*PI/n

return rootn(mag,theta,delta,k>1 ? k-1:0) if kind_of?(Complex)

return rootn(mag,theta,delta,k-1) if k>0

return mag if self > 0

return -mag if n&1 == 1

return rootn(mag,theta)

end

Settng option k = 0 in the declaration makes root return default values if k not used, so x.root(n) and x.root(n,0)are equivalent. The second line creates the parameters mag, theta, (in abreviated form) and delta for the currentnumber object and n. Since roots are de�ned from k=1..n, 1 is subtracted if k>0 to use in rootn. The �rst returndoes this for all complex numbers, the second for all reals when k>0. The other real default conditons then follow.

2

Page 3: Roots in Ruby

Whereas root returns a single root value, roots returns a collection of root values, using the syntax x.roots(n,opt).Examples: 9342.roots(9); -89.roots(4,'real'); (2.2).roots 3,'Im'; 12.roots 4.'e'; 74.roots 5,'Odd'. (In Ruby 1.9.xoptions can be symbols so you can also do: 24.roots(5, :e).)

The code for roots simply consists of a case statement which checks the options and returns an array (possiblyempty) of the appropriate root values.

Listing 4

def roots(n,opt)

mag = abs**n**-1 ; theta = arg/n ; delta = 2*PI/n

roots = []

case opt

when /^(o|O)/

0.step(n-1,2) {|k| roots <�< rootn(mag,theta,delta,k)}

when /^(e|E)/

1.step(n-1,2) {|k| roots <�< rootn(mag,theta,delta,k)}

when /^(r|R)/

n.times {|k| x=rootn(mag,theta,delta,k); roots <�< x if x.imag == 0}

when /^(i|I)/

n.times {|k| x=rootn(mag,theta,delta,k); roots <�< x if x.real == 0}

when /^(c|C)/

n.times {|k|

x=rootn(mag,theta,delta,k); roots <�< x unless

x.imag == 0 || x.real == 0}

else

n.times {|k| roots <�< rootn(mag,theta,delta,k)}

end

return roots

end

We now just need to package up this code in a Root Module, with �le name roots.rb, shown in Listing 5.Inside the Root Module, the require method �rst loads the Complex library, which provides the arg, real,

and imag methods. Then the Math library is 'included' into the workspace, which has the functions Math::cos,Math::sin, and constant Math::PI. Now they all can be used within the workspace in shortened form. The code forroot and roots is created (implicitly) as public methods, while the rootn code is made 'private', accessible only toroot|s (a user can't do x.rootn). Included in the code for these �nal versions of root|s are error checks on the inputparrameters, and code comments.

The �nal step in the module is to 'mixin' the Root Module into the base number class, class Numeric, byincluding Root into it. This makes root and roots useable on all Ruby numeric class objects (reals, �oats, �xnums,bignums, integers, rationals, and complex). So a user merely needs to put the �le roots.rb into their Ruby libdirectory (or put its location in their Ruby load path) and do a require 'roots' in their code (or an irb session) toextend all Ruby number classes with these methods.

Using root and roots allows you to now easily answer questions such as: How many complex roots of x?:x.roots(n,'c').size; What's the 4th 5th root of (4+9i)?: Complex (4,9).root(5,4); or What are the 3rd quadrant 12throots of 239?: 239.roots(12).map{|r| r if r.real < 0 && r.imag < 0}.compact.

The fully annotated source code for roots.rb is located here: https://gist.github.com/422636

Email: [email protected]

3

Page 4: Roots in Ruby

Listing 5# file roots.rb

module Roots

require 'complex'

include Math

def root(n,k=0) # return kth (1..n) value of root n or default for k=0

raise "Root n not an integer > 0" unless n.kind_of?(Integer) && n>0

raise "Index k not an integer >= 0" unless k.kind_of?(Integer) && k>=0

return self if n == 1 || self == 0

mag = abs**n**-1 ; theta = arg/n ; delta = 2*PI/n

return rootn(mag,theta,delta,k>1 ? k-1:0) if kind_of?(Complex)

return rootn(mag,theta,delta,k-1) if k>0 # kth root n for any real

return mag if self > 0 # pos real default

return -mag if n&1 == 1 # neg real default, n odd

return rootn(mag,theta) # neg real default, n even, 1st ccw root

end

def roots(n,opt=0) # return array of root n values, [] option not valid

raise "Root n not an integer > 0" unless n.kind_of?(Integer) && n>0

raise "Invalid option" unless opt == 0 || opt =~ /^(c|e|i|o|r|C|E|I|O|R)/

return [self] if n == 1 || self == 0

mag = abs**n**-1 ; theta = arg/n ; delta = 2*PI/n

roots = []

case opt

when /^(o|O)/ # odd roots 1,3,5...

0.step(n-1,2) {|k| roots <�< rootn(mag,theta,delta,k)}

when /^(e|E)/ # even roots 2,4,6...

1.step(n-1,2) {|k| roots <�< rootn(mag,theta,delta,k)}

when /^(r|R)/ # real roots Complex(x,0) = (x+i0)

n.times {|k| x=rootn(mag,theta,delta,k); roots <�< x if x.imag == 0}

when /^(i|I)/ # imaginry roots Complex(0,y) = (0+iy)

n.times {|k| x=rootn(mag,theta,delta,k); roots <�< x if x.real == 0}

when /^(c|C)/ # complex roots Complex(x,y) = (x+iy)

n.times {|k| x=rootn(mag,theta,delta,k); roots <�< x if x.arg*2 % PI != 0}

else # all n roots

n.times {|k| roots <�< rootn(mag,theta,delta,k)}

end

return roots

end

private # don't show as methods in mixin class

# Alias sin|cos to fix C lib errors to get 0.0 values for X|Y axis angles.

def sine(x); cos(x).abs == 1 ? 0 : sin(x) end

def cosine(x); sin(x).abs == 1 ? 0 : cos(x) end

def rootn(mag,theta,delta=0,k=0) # root k of n of real|complex

angle_n = theta + k*delta

mag*Complex(cosine(angle_n),sine(angle_n))

end

end

# Mixin 'root' and 'roots' for use with all real/complex numbers

class Numeric; include Roots end

4