Você está na página 1de 4

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 these operations will only return one default value. So how do you nd the rest of them? I show here how to extend Ruby 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) = aeiarg = a[cos(arg)+isin(arg)], where the magnitude a = x2 + y 2 and angle arg = tan1 y/x. So a real number can be treated as a complex number whose imaginary part is 0, where the 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 = (aeiarg )1/n 3) rootn,k = (aei(arg+2k) )1/n 4) rootn,k = (a)1/n (ei(arg+2k) )1/n 5) rootn,k = a1/n ei(arg+2k)/n 6) rootn,k = a1/n [cos(arg/n + 2k/n) + i sin(arg/n + 2k/n)] and dening 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-1
delta

This shows that for a root n there are n distinct root values, starting at initial angle theta (k = 0), evenly spaced 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 are mapped 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 for8 it's its second ccw root (2). In fact, for an odd numbered root of a negative real its 'calculator' root will always be the middle numbered root (on the 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) (calculator mode), 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, the second k=2, etc. I want roots to do just what it sounds like it should do, which is return a collection (an array) of root values. The default x.roots(n) will return all n roots. When using options, x.roots(n,opt) will return a subset of 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, which will be the workhorse function inside of root and roots. It takes 4 parameters and returns the results of the last operation. It uses the Complex(x,y) function from the Ruby standard lib Complex, and the cos|sin functions from the 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|y axes 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 of the other function is 1.0 (on an axis). This is a x to this problem which gives correct results for all root values that 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 its object 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 specic conditions of real numbers have to be taken into consideration. When taking the root of a postive real standard Ruby will return the positive real value 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 Ruby is 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 current number object and n. Since roots are dened from k=1..n, 1 is subtracted if k>0 to use in rootn. The rst return does this for all complex numbers, the second for all reals when k>0. The other real default conditons then follow.

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.x options 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 (possibly empty) 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 for root and roots is created (implicitly) as public methods, while the rootn code is made 'private', accessible only to root|s (a user can't do x.rootn). Included in the code for these nal versions of root|s are error checks on the input parrameters, and code comments. The nal step in the module is to 'mixin' the Root Module into the base number class, class Numeric, by including 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 lib directory (or put its location in their Ruby load path) and do a require 'roots' in their code (or an irb session) to extend 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 12th roots 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: jzakiya@gmail.com

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

Você também pode gostar