Updated 2012-05-17 01:42:53 by RLE

Arjen Markus (12 may 2004) Complex numbers fascinate me in a peculiar way - in my professional life I very rarely have to deal with them, unfortunately, but they make certain mathematical problems much and much easier. Oh well, here is a script that implements complex numbers as lists of two floating-point numbers. By assuming the preprocessing step has been done, the procedures can be very short and fast.

AM (13 may 2004) I made a few mistakes here - thanks to tcltest I was able to correct them. The corrected script is below.

GWM for even more complex numbers see Quaternion. NB the scripts on this page need Tcl8.4.7 at least (or they did not work with my 8.4.5).

Am Really? I developed this using 8.4.1. Can you indicate what did not work? (One thing that did not work: the namespace import - corrected this (note: the package can be found in Tcllib 1.7 - math module) GWM Err really. I have installed 8.4.7 and it works now. Sadly I cannot go through uninstall/reinstall without extra coffee. I remember I got messages about 'namespace complexnumbers not defined' (Possibly with more qualifiers). These routines are now in the distro, so I am not sure if the 'tests' at the end exercised the code on this page or the code in the Tcl distro. The changes in this page since then could have cured this problem (the main changes on comparing files were namespace import ::math::complexnumbers::*; set a [complex 1 0]; set b [complex 0 1]; set c [complex -1 0]).

GPS: Arjen and Richard come up with interesting contraptions. I hadn't heard of complex numbers before this page, and I suspect many others have not as well. To that end I found this page useful: http://www.clarku.edu/~djoyce/complex/plane.html

For some reason this reminds me of Funge.

AM Fixed a small bug in [cos], noted by Oscar Andres Lopez] (26 october 2005)
 # qcomplex.tcl --
 #    Small module for dealing with complex numbers
 #    The design goal was to make the operations as fast
 #    as possible, not to offer a nice interface. So:
 #    - complex numbers are represented as lists of two elements
 #    - there is hardly any error checking, all arguments are assumed
 #      to be complex numbers already (with a few obvious exceptions)
 #    Missing:
 #    the inverse trigonometric functions and the hyperbolic functions
 #

 namespace eval ::math::complexnumbers {
    namespace export + - / * conj exp sin cos tan real imag mod arg log pow sqrt tostring
 }

 # complex --
 #    Create a new complex number
 # Arguments:
 #    real      The real part
 #    imag      The imaginary part
 # Result:
 #    New complex number
 #
 proc ::math::complexnumbers::complex {real imag} {
    return [list $real $imag]
 }

 # binary operations --
 #    Implement the basic binary operations
 # Arguments:
 #    z1        First argument
 #    z2        Second argument
 # Result:
 #    New complex number
 #
 proc ::math::complexnumbers::+ {z1 z2} {
    set result {}
    foreach c $z1 d $z2 {
       lappend result [expr {$c+$d}]
    }
    return $result
 }
 proc ::math::complexnumbers::- {z1 {z2 {}}} {
    if { $z2 == {} } {
       set z2 $z1
       set z1 {0.0 0.0}
    }
    set result {}
    foreach c $z1 d $z2 {
       lappend result [expr {$c-$d}]
    }
    return $result
 }
 proc ::math::complexnumbers::* {z1 z2} {
    set result {}
    foreach {c1 d1} $z1 {break}
    foreach {c2 d2} $z2 {break}

    return [list [expr {$c1*$c2-$d1*$d2}] [expr {$c1*$d2+$c2*$d1}]]
 }
 proc ::math::complexnumbers::/ {z1 z2} {
    set result {}
    foreach {c1 d1} $z1 {break}
    foreach {c2 d2} $z2 {break}

    set denom [expr {$c2*$c2+$d2*$d2}]
    return [list [expr {($c1*$c2+$d1*$d2)/$denom}] \
                 [expr {(-$c1*$d2+$c2*$d1)/$denom}]]
 }

 # unary operations --
 #    Implement the basic unary operations
 # Arguments:
 #    z1        Argument
 # Result:
 #    New complex number
 #
 proc ::math::complexnumbers::conj {z1} {
    foreach {c d} $z1 {break}
    return [list $c [expr {-$d}]]
 }
 proc ::math::complexnumbers::real {z1} {
    foreach {c d} $z1 {break}
    return $c
 }
 proc ::math::complexnumbers::imag {z1} {
    foreach {c d} $z1 {break}
    return $d
 }
 proc ::math::complexnumbers::mod {z1} {
    foreach {c d} $z1 {break}
    return [expr {hypot($c,$d)}]
 }
 proc ::math::complexnumbers::arg {z1} {
    foreach {c d} $z1 {break}
    if { $c != 0.0 || $d != 0.0 } {
       return [expr {atan2($d,$c)}]
    } else {
       return 0.0
    }
 }

 # elementary functions --
 #    Implement the elementary functions
 # Arguments:
 #    z1        Argument
 #    z2        Second argument (if any)
 # Result:
 #    New complex number
 #
 proc ::math::complexnumbers::exp {z1} {
    foreach {c d} $z1 {break}
    return [list [expr {exp($c)*cos($d)}] [expr {exp($c)*sin($d)}]]
 }
 proc ::math::complexnumbers::cos {z1} {
    foreach {c d} $z1 {break}
    return [list [expr {cos($c)*cosh($d)}] [expr {-sin($c)*sinh($d)}]]

 }
 proc ::math::complexnumbers::sin {z1} {
    foreach {c d} $z1 {break}
    return [list [expr {sin($c)*cosh($d)}] [expr {cos($c)*sinh($d)}]]
 }
 proc ::math::complexnumbers::tan {z1} {
    return [/ [sin $z1] [cos $z1]]
 }
 proc ::math::complexnumbers::log {z1} {
    return [list [expr {log([mod $z1])}] [arg $z1]]
 }
 proc ::math::complexnumbers::sqrt {z1} {
    set argz [expr {0.5*[arg $z1]}]
    set modz [expr {sqrt([mod $z1])}]
    return [list [expr {$modz*cos($argz)}] [expr {$modz*sin($argz)}]]
 }
 proc ::math::complexnumbers::pow {z1 z2} {
    return [exp [* [log $z1] $z2]]
 }
 # transformational functions --
 #    Implement transformational functions
 # Arguments:
 #    z1        Argument
 # Result:
 #    String like 1+i
 #
 proc ::math::complexnumbers::tostring {z1} {
    foreach {c d} $z1 {break}
    if { $d == 0.0 } {
       return "$c"
    } else {
       if { $c == 0.0 } {
          if { $d == 1.0 } {
             return "i"
          } elseif { $d == -1.0 } {
             return "-i"
          } else {
             return "${d}i"
          }
       } else {
          if { $d > 0.0 } {
             if { $d == 1.0 } {
                return "$c+i"
             } else {
                return "$c+${d}i"
             }
          } else {
             if { $d == -1.0 } {
                return "$c-i"
             } else {
                return "$c${d}i"
             }
          }
       }
    }
 }

 #
 # Some tests
 #
 namespace import ::math::complexnumbers::*
 set a [complex 1 0]
 set b [complex 0 1]
 set c [complex -1 0]
 puts "a = [tostring $a]; b = [tostring $b]; c = [tostring c]"
 puts "a+b= [+ $a $b] (= 1  1)"
 puts "a-b= [- $a $b] (= 1 -1)"
 puts "-a = [- $a] (= -1 0)"
 puts "a*a = [* $a $a] (= 1 0)"
 puts "b*b = [* $b $b] (= -1 0)"
 puts "a/b = [/ $a $b] (= 0 -1)"
 puts "exp(a) = [exp $a] (= e 0)"
 puts "exp(b) = [exp $b] (= cos(1) sin(1))"
 puts "cos(b) = [cos $b]"
 puts "sin(b) = [sin $b]"
 puts "tan(b) = [tan $b]"
 puts "conj(b) = [conj $b] (= 0 -1)"
 puts "mod(b) = [mod $b] (= 1)"
 puts "arg(b) = [arg $b] (= pi/2)"
 puts "real(b) = [real $b] (= 0)"
 puts "imag(b) = [imag $b] (= 1)"
 puts "sqrt(c) = [sqrt $c] (= 0 1)"
 puts "log(c) = [log $c] (= 0 pi)"
 puts "tostring(0 1) = [tostring {0 1}] (= i)"
 puts "tostring(1 -1) = [tostring {1 -1}] (= 1-i)"
 puts "tostring(1 0) = [tostring {1 0}] (= 1)"
 puts "pow(1,10i) = [pow {1 0} {0 10}] (= 1)"
 puts "pow(i,i) = [pow {0 1} {0 1}] (= e**(-pi/2))"
 puts "exp(i*pi) = [exp [list 0 [expr {acos(-1)}]]] (= -1)"

See also Complex math made simple - Complex math with TOOT

AM Recent discussions regarding the possibility to expand [expr] to take more data types into account, complex numbers for instance or vector-valued variables have led me to create two new pages: On mathematical notation - a very nice overview by Lars Hellstrom, and as I was inspired by the solution he proposed, Vector spaces.