'From Squeak3.7gamma of ''17 July 2004'' [latest update: #5978] on 22 July 2004 at 11:50:11 am'! "Change Set: Complex Date: 16 July 2004 Author: Matej Kosik md: This is just a first stab at an implementation of Complex. This will definitly need improvements, but the base functionality is there. -removed Matrix, fixed undef references -added test class. v2: added two methods for save devision (Boris Gaertner) and tests. I represent a complex number. real -- real part of the complex number imaginary -- imaginary part of the complex number Complex number constructors: 5 i 6 + 7 i. 5.6 - 8 i. Complex real: 10 imaginary: 5. Complex abs: 5 arg: (Float pi / 4) Arithmetic operation with other complex or non-complex numbers work. (5 - 6 i) + (-5 + 8 i). Arithmetic between two complex numbers. 5 * (5 - 6 i). Arithmetic between a non-complex and a complex number. It is also possible to perform arithmetic operations between a complex number and a array of (complex) numbers: 2 * {1 + 2i. 3 + 4i. 5 + 6i} 5 + 5i * {1 + 2i. 3. 5 + 6i} It behaves analogously as it is with normal numbers and an array. NOTE: Although Complex something similiar to the Smalltalk's Number class, it would not be a good idea to make a Complex to be a subclass of a Number because: - Number is subclass of Magnitude and Complex is certainly not a magnitude. Complex does not behave very well as a Magnitude. Operations such as < > <= >= do not have sense in case of complex numbers. - Methods in the following Number methods' categories do not have sense for a Complex numbers trucation and round off testing intervals comparing - However the following Number methods' categories do have sense for a Complex number arithmetic (with the exception of operation // \\ quo: rem: mathematical functions Thus Complex is somewhat similar to a Number but it is not a subclass of it. Some operations we would like to inherit (e.g. #abs, #negated, #reciprocal) but some of the Number operation do not have sens to inherit or to overload. Classes are not always neat mechanism. !!!!!! We had to COPY the implementation of the abs negated reciprocal log: isZero reciprocal ... methods from the Number class to the Complex class. Awful solution. Now I begin to appreciate the Self. Missing methods String | converting | asComplex Complex | mathematical functions | arcSin Complex | mathematical functions | arcCos Complex | mathematical functions | arcTan "! Object subclass: #Complex instanceVariableNames: 'real imaginary' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Numbers'! !Complex commentStamp: 'mk 10/31/2003 22:19' prior: 0! I represent a complex number. real -- real part of the complex number imaginary -- imaginary part of the complex number Complex number constructors: 5 i 6 + 7 i. 5.6 - 8 i. Complex real: 10 imaginary: 5. Complex abs: 5 arg: (Float pi / 4) Arithmetic operation with other complex or non-complex numbers work. (5 - 6 i) + (-5 + 8 i). "Arithmetic between two complex numbers." 5 * (5 - 6 i). "Arithmetic between a non-complex and a complex number." It is also possible to perform arithmetic operations between a complex number and a array of (complex) numbers: 2 * {1 + 2i. 3 + 4i. 5 + 6i} 5 + 5i * {1 + 2i. 3. 5 + 6i} It behaves analogously as it is with normal numbers and an array. NOTE: Although Complex something similiar to the Smalltalk's Number class, it would not be a good idea to make a Complex to be a subclass of a Number because: - Number is subclass of Magnitude and Complex is certainly not a magnitude. Complex does not behave very well as a Magnitude. Operations such as < > <= >= do not have sense in case of complex numbers. - Methods in the following Number methods' categories do not have sense for a Complex numbers trucation and round off testing intervals comparing - However the following Number methods' categories do have sense for a Complex number arithmetic (with the exception of operation // \\ quo: rem: mathematical functions Thus Complex is somewhat similar to a Number but it is not a subclass of it. Some operations we would like to inherit (e.g. #abs, #negated, #reciprocal) but some of the Number operation do not have sens to inherit or to overload. Classes are not always neat mechanism. !!!!!! We had to COPY the implementation of the abs negated reciprocal log: isZero reciprocal ... methods from the Number class to the Complex class. Awful solution. Now I begin to appreciate the Self. Missing methods String | converting | asComplex Complex | mathematical functions | arcSin Complex | mathematical functions | arcCos Complex | mathematical functions | arcTan! TestCase subclass: #ComplexTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Tests-Kernel-Numbers'! !Object methodsFor: 'testing' stamp: 'mk 10/27/2003 17:33'! isComplex "Answer true if receiver is a Complex number. False by default." ^ false ! ! !Collection methodsFor: 'adapting' stamp: 'mk 10/27/2003 21:48'! adaptToComplex: rcvr andSend: selector "If I am involved in arithmetic with a scalar, return a Collection of the results of each element combined with the scalar in that expression." ^ self collect: [:element | rcvr perform: selector with: element]! ! !Complex methodsFor: 'accessing' stamp: 'mk 10/27/2003 17:39'! imaginary ^ imaginary! ! !Complex methodsFor: 'accessing' stamp: 'mk 10/27/2003 17:39'! real ^ real! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/21/2004 11:25'! * anObject "Answer the result of multiplying the receiver by aNumber." | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ (a * c) - (b * d). newImaginary _ (a * d) + (b * c). ^ Complex real: newReal imaginary: newImaginary] ifFalse: [^ anObject adaptToComplex: self andSend: #*]! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 1/18/2004 23:31'! + anObject "Answer the sum of the receiver and aNumber." | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ a + c. newImaginary _ b + d. ^ Complex real: newReal imaginary: newImaginary] ifFalse: [^ anObject adaptToComplex: self andSend: #+]! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:45'! - anObject "Answer the difference between the receiver and aNumber." | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ a - c. newImaginary _ b - d. ^ Complex real: newReal imaginary: newImaginary] ifFalse: [^ anObject adaptToComplex: self andSend: #-]! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:45'! / anObject "Answer the result of dividing receiver by aNumber" | a b c d newReal newImaginary | anObject isComplex ifTrue: [a _ self real. b _ self imaginary. c _ anObject real. d _ anObject imaginary. newReal _ ((a * c) + (b * d)) / ((c * c) + (d * d)). newImaginary _ ((b * c) - (a * d)) / ((c * c) + (d * d)). ^ Complex real: newReal imaginary: newImaginary]. ^ anObject adaptToComplex: self andSend: #/.! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 20:48'! abs "Answer the distance of the receiver from zero (0 + 0 i)." ^ ((real * real) + (imaginary * imaginary)) sqrt! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 22:08'! arg "Answer the argument of the receiver." self isZero ifTrue: [self error: 'zero has no argument.']. 0 < real ifTrue: [^ (imaginary / real) arcTan]. 0 = real ifTrue: [0 < imaginary ifTrue: [^ Float pi / 2] ifFalse: [^ (Float pi / 2) negated]]. real < 0 ifTrue: [0 <= imaginary ifTrue: [^ (imaginary / real) arcTan + Float pi] ifFalse: [^ (imaginary / real) arcTan - Float pi]]! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:48'! divideFastAndSecureBy: anObject "Answer the result of dividing receiver by aNumber" " Both operands are scaled to avoid arithmetic overflow. This algorithm works for a wide range of values, and it needs only three divisions. Note: #reciprocal uses #/ for devision " | r d newReal newImaginary | anObject isComplex ifTrue: [anObject real abs > anObject imaginary abs ifTrue: [r _ anObject imaginary / anObject real. d _ r*anObject imaginary + anObject real. newReal _ r*imaginary + real/d. newImaginary _ r negated * real + imaginary/d. ] ifFalse: [r _ anObject real / anObject imaginary. d := r*anObject real + anObject imaginary. newReal _ r*real + imaginary/d. newImaginary _ r*imaginary - real/d. ]. ^ Complex real: newReal imaginary: newImaginary]. ^ anObject adaptToComplex: self andSend: #/.! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:48'! divideSecureBy: anObject "Answer the result of dividing receiver by aNumber" " Both operands are scaled to avoid arithmetic overflow. This algorithm works for a wide range of values, but it requires six divisions. #divideFastAndSecureBy: is also quite good, but it uses only 3 divisions. Note: #reciprocal uses #/ for devision" | s ars ais brs bis newReal newImaginary | anObject isComplex ifTrue: [s := anObject real abs + anObject imaginary abs. ars := self real / s. ais := self imaginary / s. brs := anObject real / s. bis := anObject imaginary / s. s := brs squared + bis squared. newReal _ ars*brs + (ais*bis) /s. newImaginary _ ais*brs - (ars*bis)/s. ^ Complex real: newReal imaginary: newImaginary]. ^ anObject adaptToComplex: self andSend: #/.! ! !Complex methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 19:33'! negated "Answer a Number that is the negation of the receiver." ^0 - self! ! !Complex methodsFor: 'arithmetic' stamp: 'md 7/22/2004 11:47'! reciprocal "Answer 1 divided by the receiver. Create an error notification if the receiver is 0." self = 0 ifTrue: [^ (ZeroDivide dividend: self) signal] ifFalse: [^1 / self] ! ! !Complex methodsFor: 'comparing' stamp: 'mk 1/18/2004 23:37'! = anObject anObject isComplex ifTrue: [^ (real = anObject real) & (imaginary = anObject imaginary)] ifFalse: [^ anObject adaptToComplex: self andSend: #=]! ! !Complex methodsFor: 'comparing' stamp: 'mk 10/27/2003 20:35'! hash "Hash is reimplemented because = is implemented." ^ real hash bitXor: imaginary hash.! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 21:51'! adaptToCollection: rcvr andSend: selector "If I am involved in arithmetic with a Collection, return a Collection of the results of each element combined with me in that expression." ^ rcvr collect: [:element | element perform: selector with: self]! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 18:32'! adaptToFloat: rcvr andSend: selector "If I am involved in arithmetic with a Float, convert it to a Complex number." ^ rcvr asComplex perform: selector with: self! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 18:32'! adaptToFraction: rcvr andSend: selector "If I am involved in arithmetic with a Fraction, convert it to a Complex number." ^ rcvr asComplex perform: selector with: self! ! !Complex methodsFor: 'converting' stamp: 'mk 10/27/2003 18:31'! adaptToInteger: rcvr andSend: selector "If I am involved in arithmetic with an Integer, convert it to a Complex number." ^ rcvr asComplex perform: selector with: self! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! cos "Answer receiver's cosine." | iself | iself _ 1 i * self. ^ (iself exp + iself negated exp) / 2! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 21:34'! cosh "Answer receiver's hyperbolic cosine." ^ (self exp + self negated exp) / 2! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! exp "Answer the exponential of the receiver." ^ real exp * (imaginary cos + (1 i * imaginary sin))! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! ln "Answer the natural log of the receiver." ^ self arg ln + (1 i * self arg)! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 22:05'! log: aNumber "Answer the log base aNumber of the receiver." ^self ln / aNumber ln! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/16/2004 16:16'! sin "Answer receiver's sine." | iself | iself _ 1 i * self. ^ (iself exp - iself negated exp) / 2 i! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 21:33'! sinh "Answer receiver's hyperbolic sine." ^ (self exp - self negated exp) / 2! ! !Complex methodsFor: 'mathematical functions' stamp: 'md 7/20/2004 12:02'! squared "Answer the receiver multipled by itself." ^self * self! ! !Complex methodsFor: 'mathematical functions' stamp: 'mk 10/27/2003 22:04'! tan "Answer receivers tangent." ^ self sin / self cos! ! !Complex methodsFor: 'printing' stamp: 'mk 10/27/2003 18:02'! printOn: aStream real printOn: aStream. aStream nextPut: Character space. 0 <= imaginary ifTrue: [aStream nextPut: $+] ifFalse: [aStream nextPut: $-]. aStream nextPut: Character space. imaginary abs printOn: aStream. aStream nextPut: Character space. aStream nextPut: $i ! ! !Complex methodsFor: 'private' stamp: 'mk 10/27/2003 17:26'! imaginary: aNumber imaginary _ aNumber.! ! !Complex methodsFor: 'private' stamp: 'mk 10/27/2003 17:26'! real: aNumber real _ aNumber.! ! !Complex methodsFor: 'testing' stamp: 'mk 10/27/2003 17:33'! isComplex ^ true! ! !Complex methodsFor: 'testing' stamp: 'mk 10/27/2003 20:06'! isZero ^ self = 0! ! !Complex class methodsFor: 'instance creation' stamp: 'mk 10/27/2003 21:03'! abs: aNumber1 arg: aNumber2 | real imaginary | real _ aNumber1 * aNumber2 cos. imaginary _ aNumber1 * aNumber2 sin. ^ real + imaginary i! ! !Complex class methodsFor: 'instance creation' stamp: 'mk 10/27/2003 17:28'! new ^ self real: 0 imaginary: 0! ! !Complex class methodsFor: 'instance creation' stamp: 'mk 10/27/2003 17:27'! real: aNumber1 imaginary: aNumber2 | newComplex | newComplex _ super new. newComplex real: aNumber1; imaginary: aNumber2. ^ newComplex! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:00'! testAbs "self run: #testAbs" "self debug: #testAbs" | c | c := (6 - 6 i). self assert: c abs = 72 sqrt. ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 13:59'! testAdding "self run: #testAdding" | c | c := (5 - 6 i) + (-5 + 8 i). "Complex with Complex" self assert: (c = (0 + 2 i)).! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:02'! testArg "self run: #testArg" "self debug: #testArg" | c | c := (0 + 5 i) . self assert: c arg = (Float pi/ 2). ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:13'! testComplexCollection "self run: #testComplexCollection" "self debug: #testComplexCollection" | array array2 | array := Array with: 1 + 2i with: 3 + 4i with: 5 + 6i. array2 := 2 * array. array with: array2 do: [:one :two | self assert: (2 * one) = two ] ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:16'! testConversion "self run: #testConversion" "self debug: #testConversion" self assert: ((1 + 2i) + 1) = (2 + 2 i). self assert: (1 + (1 + 2i)) = (2 + 2 i). self assert: ((1 + 2i) + 1.0) = (2.0 + 2 i). self assert: (1.0 + (1 + 2i)) = (2.0 + 2 i). self assert: ((1 + 2i) + (2/3)) = ((5/3) + 2 i ). self assert: ((2/3) + (1 + 2i)) = ((5/3) + 2 i )! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 13:59'! testCreation "self run: #testCreation" | c | c := 5 i. self assert: (c real = 0). self assert: (c imaginary = 5). c := 6 + 7 i. self assert: (c real = 6). self assert: ( c imaginary = 7). c := 5.6 - 8 i. self assert: (c real = 5.6). self assert: (c imaginary = -8). c := Complex real: 10 imaginary: 5. self assert: (c real = 10). self assert: (c imaginary = 5). c := Complex abs: 5 arg: (Float pi/2). self assert: (c real rounded = 0). self assert: (c imaginary = 5). ! ! !ComplexTest methodsFor: 'testing' stamp: 'md 7/22/2004 11:42'! testDivision1 "self run: #testDivision1" "self debug: #testDivision1" | c1 c2 quotient | c1 := 2.0e252 + 3.0e70 i. c2 := c1. quotient := c1 / c2. self deny: (quotient - 1) isZero. "This test fails due to the wonders of floating point arithmetic. Please have a look at Complex>>divideSecureBy: and #divideFastAndSecureBy: how this can be avoided." ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:10'! testEquality "self run: #testEquality" "self debug: #testEquality" self assert: 0i = 0. self assert: (2 - 5i) = ((1 -4 i) + (1 - 1i)). self assert: 0i isZero. self deny: (1 + 3 i) = 1. self deny: (1 + 3 i) = (1 + 2i).! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:03'! testNegated "self run: #testNegated" "self debug: #testNegated" | c | c := (2 + 5 i) . self assert: c negated = (-2 - 5i). ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:05'! testReciprocal "self run: #testReciprocal" "self debug: #testReciprocal" | c | c := (2 + 5 i). self assert: c reciprocal = ((2/29) - (5/29)i). ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 14:07'! testReciprocalError "self run: #testReciprocalError" "self debug: #testReciprocalError" | c | c := (0 i). self should: [c reciprocal] raise: ZeroDivide ! ! !ComplexTest methodsFor: 'testing' stamp: 'md 7/22/2004 11:44'! testSecureDivision1 "self run: #testSecureDivision1" "self debug: #testSecureDivision1" | c1 c2 quotient | c1 := 2.0e252 + 3.0e70 i. c2 := c1. quotient := c1 divideSecureBy: c2. self assert: (quotient - 1) isZero. ! ! !ComplexTest methodsFor: 'testing' stamp: 'md 7/22/2004 11:44'! testSecureDivision2 "self run: #testSecureDivision2" "self debug: #testSecureDivision2" | c1 c2 quotient | c1 := 2.0e252 + 3.0e70 i. c2 := c1. quotient := c1 divideFastAndSecureBy: c2. self assert: (quotient - 1) isZero. ! ! !ComplexTest methodsFor: 'testing' stamp: 'sd 7/17/2004 13:24'! testSquared "self run: #testSquared" "self debug: #testSquared" | c c2 | c := (6 - 6 i). c2 := (c squared). self assert: c2 imaginary = -72. self assert: c2 real = 0.! ! !Number methodsFor: 'arithmetic' stamp: 'mk 10/27/2003 21:00'! arg "Answer the argument of the receiver (see Complex | arg)." self isZero ifTrue: [self error: 'Zero (0 + 0 i) does not have an argument.']. 0 < self ifTrue: [^ 0] ifFalse: [^ Float pi]! ! !Number methodsFor: 'converting' stamp: 'mk 10/27/2003 18:17'! i ^ Complex real: 0 imaginary: self! ! !Float methodsFor: 'converting' stamp: 'mk 10/27/2003 18:16'! adaptToComplex: rcvr andSend: selector "If I am involved in arithmetic with a Complex number, convert me to a Complex number." ^ rcvr perform: selector with: self asComplex! ! !Float methodsFor: 'converting' stamp: 'mk 10/27/2003 17:46'! asComplex "Answer a Complex number that represents value of the the receiver." ^ Complex real: self imaginary: 0! ! !Fraction methodsFor: 'converting' stamp: 'mk 10/27/2003 18:13'! adaptToComplex: rcvr andSend: selector "If I am involved in arithmetic with a Complex number, convert me to a Complex number." ^ rcvr perform: selector with: self asComplex! ! !Fraction methodsFor: 'converting' stamp: 'mk 10/27/2003 18:13'! asComplex "Answer a Complex number that represents value of the the receiver." ^ Complex real: self imaginary: 0! ! !Integer methodsFor: 'converting' stamp: 'mk 10/27/2003 17:45'! adaptToComplex: rcvr andSend: selector "If I am involved in arithmetic with a Complex number, convert me to a Complex number." ^ rcvr perform: selector with: self asComplex! ! !Integer methodsFor: 'converting' stamp: 'mk 10/27/2003 17:44'! asComplex "Answer a Complex number that represents value of the the receiver." ^ Complex real: self imaginary: 0! !