Roots in Ruby
-
Upload
jabari-zakiya -
Category
Documents
-
view
275 -
download
0
description
Transcript of 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
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
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
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