{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvComplex.PAS, released on 2001-02-28. The Initial Developer of the Original Code is Peter Thörnqvist [peter3 att users dott sourceforge dott net] Portions created by Peter Thörnqvist are Copyright (C) 1997 Peter Thörnqvist. All Rights Reserved. Contributor(s): Last Modified: 2002-06-24 You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Known Issues: -----------------------------------------------------------------------------} {$I JVCL.INC} { @abstract(functions to handle complex numbers) @author(Peter Thornqvist (peter3 att users dott sourceforge dott net)) @created() @lastmod() } unit JvComplex; { @exclude This is a unit that implements functions to handle complex numbers. It is a translation of the complex.cpp files supplied with, among others, Symantec's C++ 7.0. These functions might not be public domain which means I'm up for a trial RSN... Anyway, you need at least Delphi 2.0 and the Math.pas unit (supplied with Delphi) to make this code work. Slight changes might make it compilable under Delphi 1.0 though I strongly doubt it. Delphi 1.0 doesn't have the Double type or a Math.pas unit, as far as I can remember. This unit is,of course, free. This adaption, Copyright © 1997 by Peter Thörnqvist NOTES: *. Z denotes a complex number, Z.Re is the real part and Z.Im is the imaginary part. *. all functions use a common naming scheme: a. as far as possible all functions start off with a name equal to the standard library functions, like LogXX, ExpXX, SqrtXX... b. the action taken (and the order) is denoted by the letters Z,R,I: PowZR means raise Z to the power of R (I.e. Z^R), PowRZ means raise R to the power of Z (I.e: R^Z), etc *. There are no functions to manipulate imaginary numbers directly. That's because you can't have imag numbers "floating around": The computer can't differentiate between R and I variables and would treat them both as reals, generating errors. Instead, you must "hide" the imaginary number in a TComplex variable where Z.Re := 0 and then use the functions in this library. *. The functions PowZR1 and PowZR2 does the same except for the fact that PowZR1 truncates the real part to an Integer *. All the ArcXXX functions will generate an exception if |z| > 1 *. Trig and hyperbolic functions calculations are done in radians } interface uses Math, SysUtils; type TComplex = record Re: Double; Im: Double; end; { Conversions & Extractions } { ToComplex convert R and I to complex } function ToComplex(R, I: Double): TComplex; { Real return real part of Z } function Real(Z: TComplex): Double; { Imag return imag part of Z } function Imag(Z: TComplex): Double; { Utility functions } { IsValidComplexString returns True if S is a valid complex string } function IsValidComplexString(S: string): Boolean; { IsValidComplexNumber returns True if Z is a valid complex number } function IsValidComplexNumber(Z: TComplex): Boolean; { BoolToString returns Boolean as string } function BoolToString(B: Boolean): string; { ComplexToString convert Complex to a string } function ComplexToString(Z: TComplex): string; { StringToComplex convert string to a TComplex } function StringToComplex(S: string): TComplex; { Standard operators (+,-,*,/) for complex numbers } { AddZZ add two complex numbers } function AddZZ(const Z, Z2: TComplex): TComplex; { AddZR add a complex and a real } function AddZR(const Z: TComplex; R: Double): TComplex; { SubZZ subtract two complex numbers } function SubZZ(Z, Z2: TComplex): TComplex; { SubZR subtract Z.Re from R (Z - R) } function SubZR(Z: TComplex; R: Double): TComplex; { SubRZ subtract R from Z.Re (R - Z) } function SubRZ(R: Double; Z: TComplex): TComplex; { MulZZ multiply two complex numbers } function MulZZ(Z, Z2: TComplex): TComplex; { MulRZ multiply a real and a complex } function MulRZ(R: Double; Z: TComplex): TComplex; { DivZR divide Z with R ( Z/R ) - let compiler handle divbyzero} function DivZR(Z: TComplex; R: Double): TComplex; { DivRZ divide R with Z ( R/Z) - let compiler handle divbyzero} function DivRZ(R: Double; Z: TComplex): TComplex; { DivZZ divide two complex numbers } function DivZZ(Z, Z2: TComplex): TComplex; { Special functions } { AbsZ return absolute value of Z. ( sqrt(x*x + y*y)) } function AbsZ(Z: TComplex): Double; { ModZ same as AbsZ } function ModZ(Z: TComplex): Double; { ArgZ return the argument of Z in radians } function ArgZ(Z: TComplex): Double; { ConjugateZ return conjugate of Z ( conjugate( x + iy ) := x - iy ) } function ConjugateZ(Z: TComplex): TComplex; { NormZ return the square of the absolute value of Z } function NormZ(Z: TComplex): Double; { NegZ return negated value of complex number } function NegZ(Z: TComplex): TComplex; { EqualZZ test for equality } function EqualZZ(Z, Z2: Tcomplex): Boolean; { Trig. and hyperbolic functions } { CosZ return the cosine of Z } function CosZ(Z: TComplex): TComplex; { CoshZ return the hyperbolic cosine of Z } function CoshZ(Z: TComplex): TComplex; { SinZ return the sine of Z } function SinZ(Z: TComplex): TComplex; { ArcSinZ return the Arcsine of Z } function ArcSinZ(Z: TComplex): TComplex; { SinhZ return the hyperbolic sine of Z } function SinhZ(Z: TComplex): TComplex; { ArcSinhZ return the hyperbolic arcsine of Z } function ArcSinhZ(Z: TComplex): TComplex; { TanZ return the tan of Z } function TanZ(Z: TComplex): TComplex; { ArcTanZ return the arctan of Z } function ArcTanZ(Z: TComplex): TComplex; { ArcCosZ return the arccos of Z } function ArcCosZ(Z: TComplex): TComplex; { TanhZ retun the hyperbolic tan of Z } function TanhZ(Z: TComplex): TComplex; { ArcTanhZ return the hyperbolic arctan of Z } function ArcTanhZ(Z: TComplex): TComplex; { Math. functions } { ExpZ returns e raised to Z (e^Z) } function ExpZ(Z: TComplex): TComplex; { LnZ returns the natural log of Z } function LnZ(Z: TComplex): TComplex; { Log10Z retuns the base 10 log of Z } function Log10Z(Z: TComplex): TComplex; { PolarZ return the complex number built from rect.coords. Range and Theta } function PolarZ(Range, Angle: Double): TComplex; { RectangularZ return the rectangular coordinates of Z } procedure RectangularZ(Z: TComplex; var Range, Angle: Double); { SqrtZ return the Square root of Z } function SqrtZ(Z: TComplex): TComplex; { PowZZ return Z^Z2 } function PowZZ(Z, Z2: TComplex): TComplex; { PowZR1 return Z^R (R is int) } function PowZR1(Z: TComplex; R: Integer): TComplex; { PowZR2 return Z^R (R is real) } function PowZR2(Z: TComplex; R: Double): TComplex; { PowRZ return R^Z } function PowRZ(R: Double; Z: TComplex): TComplex; implementation { Conversion } { ToComplex Convert R and I to complex } function ToComplex(R, I: Double): TComplex; begin Result.Re := R; Result.Im := I; end; { IsValidComplexString True if S is a valid complex string } function IsValidComplexString(S: string): Boolean; begin Result := True; try StringToComplex(S); except on EConvertError do Result := False; end; end; { IsValidComplexNumber True if Z is a valid complex number } function IsValidComplexNumber(Z: TComplex): Boolean; begin Result := True; try ComplexToString(Z); except on EConvertError do Result := False; end; end; { BoolToString return Boolean as string } function BoolToString(B: Boolean): string; const Bools: array [Boolean] of PChar = ('False', 'True'); begin Result := Bools[B]; end; { ComplexToString convert Z to a string Will raise EConvertError on failure. Call IsValidComplexNumber first to avoid this situation.} function ComplexToString(Z: TComplex): string; begin Result := Format('(%s ,%si)', [FloatToStr(Z.Re), FloatToStr(Z.Im)]); end; { convert string S to a TComplex } { Accepts any input that StrToFloat does, with these additions: 1. Im and Re part must be separated by any nonnumerical character 2. If the Re part has a sign, it cannot be separated from the number (I.e - 3.4 is invalid; -3.4 is OK). The Im part sign can be separate from the numerical body, although it isn't recommended. Examples of valid input: +7.8,-0.56E-12 +7.8 - 0.56E-12 (+7.8,-0.56E-12) ( +7.8 , - 0.56E-12 ) +7.8 + . 5 6 E - 1 2 etc... Examples of invalid input: +7.8-0.56E-12 ( numbers can't stick together) + 7.8,-0.56E-12 ( first '+' must be connected with the number ) +(-7.8) -0.56E-12 ( multiple signs or paranthesis in number not supported. etc... 4. The global variable DecimalSeparator (the dot in the examples) controls what character is actually used for separating the integral and fractional parts of a number. This setting is controlled by the current settings in the registry. If you are uncertain what separator your country use, check Control Panel | Country | Numbers. 5. StringToComplex will raise an EConvertError (unhandled) if input isn't valid. To avoid this, call IsValidComplexString first. } function StringToComplex(S: string): TComplex; const ValidNum = ['+', '-', '0'..'9', 'E', 'e']; var I: Integer; T: string; begin T := ''; I := 1; if S = '' then Exit; while I <= Length(S) do begin if (S[I] in ValidNum) or (S[I] = DecimalSeparator) then T := T + S[I] else if Length(T) > 0 then Break; Inc(I); end; Result.Re := StrToFloat(T); T := ''; while I <= Length(S) do begin if (S[I] in ValidNum) or (S[I] = DecimalSeparator) then T := T + S[I]; Inc(I); end; Result.Im := StrToFloat(T); end; { Real return real part of Z } function Real(Z: TComplex): Double; begin Result := Z.Re; end; { Imag return imag part of Z } function Imag(Z: TComplex): Double; begin Result := Z.Im; end; { Standard Operators } { AddZZ add two complex numbers: (x + yi) + (v + wi) = (x + v) + I(y + w)} function AddZZ(const Z, Z2: TComplex): TComplex; begin Result := ToComplex(Z.Re + Z2.Re, Z.Im + Z2.Im); end; { AddZR add a complex and a real: r + (x + yi) = (r + x) + yi} function AddZR(const Z: TComplex; R: Double): TComplex; begin Result := AddZZ(Z, ToComplex(R, 0)); end; { SubZZ subtract two complex numbers: (x + yi) - (v + wi) = (x - v) + I(y - w) } function SubZZ(Z, Z2: TComplex): TComplex; begin Result := ToComplex(Z.Re - Z2.Re, Z.Im - Z2.Im); end; { SubZR subtract Z.Re from R: (x +yi) - r = (x-r) + yi } function SubZR(Z: TComplex; R: Double): TComplex; begin Result := SubZZ(Z, ToComplex(R, 0)); end; { SubRZ subtract R from Z.Re: r - (x + yi) = (r-x) - yi } function SubRZ(R: Double; Z: TComplex): TComplex; begin Result := SubZZ(ToComplex(R, 0), Z); end; { MulZZ multiply two complex numbers: (x + yi) * ( v + wi) = (xv - yw) + I(xw +yv)} function MulZZ(Z, Z2: TComplex): TComplex; begin Result := ToComplex(Z.Re * Z2.Re - Z.Im * Z2.Im, Z.Re * Z2.Im + Z.Im * Z2.Re); end; { MulRZ multiply a real and a complex: r(x+yi) = rx + ryi } function MulRZ(R: Double; Z: TComplex): TComplex; begin Result := MulZZ(ToComplex(R, 0), Z); end; { DivZR divide Z with R ( Z/R ) - let compiler handle divbyzero: (x+yi) / r := x/r + yi/r } function DivZR(Z: TComplex; R: Double): TComplex; begin Result := ToComplex(Z.Re / R, Z.Im / R); end; { DivRZ divide R with Z ( R/Z) - let compiler handle divbyzero: r/(x + yi) := r/x + r/yi} function DivRZ(R: Double; Z: TComplex): TComplex; begin if (Z.Re = 0) and (Z.Im <> 0) then Result := ToComplex(0, R / Z.Im) else if (Z.Im = 0) and (Z.Re <> 0) then Result := ToComplex(R / Z.Re, 0) else Result := ToComplex(R / Z.Re, R / Z.Im); end; { DivZZ divide Z with Z2 ( Z/Z2) - let compiler handle divbyzero } function DivZZ(Z, Z2: TComplex): TComplex; var Zb, Z2b: TComplex; begin Zb := MulZZ(Z, ConjugateZ(Z2)); Z2b := MulZZ(Z2, ConjugateZ(Z2)); { this should be Re now (Im := 0) } Result := DivZR(Zb, Real(Z2b)); end; { Misc. complex specific } { AbsZ return absolute value of Z. ( sqrt(x*x + y*y)) } function AbsZ(Z: TComplex): Double; var x, y: Double; begin x := Abs(Z.Re); y := Abs(Z.Im); if x = 0 then Result := y else if y = 0 then Result := x else begin if x > y then Result := x * Sqrt(1 + Power(y / x, 2)) else Result := y * Sqrt(1 + Power(x / y, 2)); end; end; { ModZ is the same as AbsZ } function ModZ(Z: TComplex): Double; begin Result := AbsZ(Z); end; { ArgZ return the argument of Z in radians: z = x + yi -> arctan(y/x) } function ArgZ(Z: TComplex): Double; begin Result := ArcTan2(Z.Im, Z.Re); end; { ConjugateZ return conjugate of Z ((x + iy)~ = x - iy ) } function ConjugateZ(Z: TComplex): TComplex; begin Result := ToComplex(Z.Re, -Z.Im); end; { NormZ return the square of the absolute value of Z: |x + yi|^2 } function NormZ(Z: TComplex): Double; begin Result := Z.Re * Z.Re + Z.Im * Z.Im; end; { NegZ return negated value of complex number: x + yi = -x - yi } function NegZ(Z: TComplex): TComplex; begin Result := ToComplex(-Z.Re, -Z.Im); end; { EqualZZ test for equality } function EqualZZ(Z, Z2: Tcomplex): Boolean; begin Result := ((Z.Re = Z2.Re) and (Z.Im = Z2.Im)); end; { Trigonometric } { CosZ return the cosine of Z } function CosZ(Z: TComplex): TComplex; begin Result := ToComplex(Cos(Z.Re) * Cosh(Z.Im), -(Sin(Z.Re) * Sinh(Z.Im))); end; { ArcCosZ return the arccosine of Z. Input value must be between -1..1 and -I..I } function ArcCosZ(Z: TComplex): TComplex; begin Result := ToComplex(ArcCos(Z.Re) * ArcCosh(Z.Im), ArcSin(Z.Re) * ArcSinh(Z.Im)); end; { CoshZ return the hyperbolic cosine of Z } function CoshZ(Z: TComplex): TComplex; begin Result := ToComplex(Cosh(Z.Re) * Cos(Z.Im), Sinh(Z.Re) * Sin(Z.Im)); end; { SinZ return the sine of Z } function SinZ(Z: TComplex): TComplex; begin Result := ToComplex(Sin(Z.Re) * Cosh(Z.Im), Cos(Z.Re) * Sinh(Z.Im)); end; { ArcSinZ return the arcsine of Z. Input value must be between -1..1 and -I..I } function ArcSinZ(Z: TComplex): TComplex; begin Result := ToComplex(ArcSin(Z.Re) * ArcCosh(Z.Im), ArcCos(Z.Re) * ArcSinh(Z.Im)); end; { SinhZ return the hyperbolic sine of Z } function SinhZ(Z: TComplex): TComplex; begin Result := ToComplex(Sinh(Z.Re) * Cos(Z.Im), Cosh(Z.Re) * Sin(Z.Im)); end; { ArcSinhZ return the hyperbolic arcsine of Z } function ArcSinhZ(Z: TComplex): TComplex; begin Result := ToComplex(-Z.Im, Z.Re); Result := ArcSinZ(Result); Result := ToComplex(Result.Im, -Result.Re); end; { TanZ return the tan of Z } function TanZ(Z: TComplex): TComplex; var x, y, t: Double; begin x := 2 * Z.Re; y := 2 * z.Im; t := 1.0 / (Cos(x) + Cosh(y)); Result := ToComplex(t * Sin(x), t * Sinh(y)); end; { ArcTanZ return the arctan of Z. } function ArcTanZ(Z: TComplex): TComplex; var x, y, t: Double; begin x := 2 * Z.Re; y := 2 * z.Im; t := 1.0 / (Cos(x) + Cosh(y)); Result := ToComplex(t * ArcSin(x), t * ArcSinh(y)); end; { TanhZ retun the hyperbolic tan of Z } function TanhZ(Z: TComplex): TComplex; var x, y, t: Double; begin x := 2 * Z.Re; y := 2 * z.Im; t := 1.0 / (Cos(x) + Cosh(y)); Result := ToComplex(t * Sinh(x), t * Sin(y)); end; { ArcTanhZ return the hyperbolic arctan of Z. Input value must be between -1..1 and -I..I } function ArcTanhZ(Z: TComplex): TComplex; begin Result := ToComplex(-Z.Im, Z.Re); Result := ArcTanZ(Result); Result := ToComplex(Result.Im, -Result.Re); end; { Math Functions } { ExpZ returns e raised to Z: e^(x + yi) } function ExpZ(Z: TComplex): TComplex; var x: Double; begin x := Exp(Z.Re); Result := ToComplex(x * Cos(Z.Im), x * Sin(Z.Im)); end; { LnZ returns the natural log of Z: Ln(x + iy) } function LnZ(Z: TComplex): TComplex; begin Result := ToComplex(Ln(AbsZ(Z)), ArgZ(Z)); end; { Log10Z returns the base 10 log of Z: log(x+yi) } function Log10Z(Z: TComplex): TComplex; begin Result := ToComplex(0.2171472409516259 * Ln(NormZ(Z)), ArgZ(Z)); end; { PolarZ returns the complex number given by the rectangular coordinates Range and Angle: z = r(cos(angle) + I sin(angle)) Range is the length of the straight line extending from 0,0i to the point x,yi Angle is the angle in radians between the real axis and the point x,yi } function PolarZ(Range, Angle: Double): TComplex; begin Result := ToComplex(Range * Cos(Angle), Range * Sin(Angle)); end; { RectangularZ returns the rectangular coordinates Range and Angle of the complex number Z (see explanation above) } procedure RectangularZ(Z: TComplex; var Range, Angle: Double); begin Angle := ArgZ(Z); Range := AbsZ(Z); end; { SqrtZ return the Square root of Z: sqrt(x+yi) } function SqrtZ(Z: TComplex): TComplex; var a, b: Double; begin if (Z.Re = 0) and (Z.Im = 0) then Result := ToComplex(1, 0) else begin a := Sqrt((Abs(Z.Re) + AbsZ(Z)) * 0.5); if Z.Re >= 0 then b := Z.Im / (a + a) else begin if Z.Im < 0 then b := -a else b := a; a := Z.Im / (b + b); end; Result := ToComplex(a, b); end; end; { PowZZ return Z^Z2: (x+yi)^(v+wi) } function PowZZ(Z, Z2: TComplex): TComplex; var LogF, Phase: TComplex; begin if (Z2.Re = 0) and (Z2.Im = 0) then Result := ToComplex(1, 0) else if Z2.Im = 0 then Result := PowZR2(Z, Z2.Re) else begin LogF.Re := Ln(AbsZ(Z)); LogF.Im := ArcTan2(Z.Im, Z.Re); Phase.Re := Exp(LogF.Re * Z2.Re - LogF.Im * Z2.Im); Phase.Im := LogF.Re * Z2.Im + LogF.Im * Z2.Re; Result := ToComplex(Phase.Re * Cos(Phase.Im), Phase.Re * Sin(Phase.Im)); end; end; { PowZR1 return Z^R (R is int): (x+yi)^r } function PowZR1(Z: TComplex; R: Integer): TComplex; begin Result := PowZZ(Z, ToComplex(R, 0)); end; { PowZR2 return Z^R (R is real): (x +yi)^r } function PowZR2(Z: TComplex; R: Double): TComplex; begin Result := PowZZ(Z, ToComplex(R, 0)); end; { PowRZ return R^Z: r^(x + yi) } function PowRZ(R: Double; Z: TComplex): TComplex; begin Result := PowZZ(ToComplex(R, 0), Z); end; end.