//******************************************************************************************************
// ComplexNumber.cs - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 04/14/2016 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Data.SqlTypes;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;
namespace GSF.Core.SqlClr
{
///
/// Defines a complex number type that can be used in a SQL Server database.
///
[Serializable]
[SqlUserDefinedType(Format.Native, IsByteOrdered = true)]
public struct ComplexNumber : INullable
{
#region [ Members ]
// Fields
private bool m_isNull;
private double m_real;
private double m_imaginary;
#endregion
#region [ Properties ]
///
/// Gets a value that indicates whether the complex number is null.
///
public bool IsNull
{
get
{
return m_isNull;
}
}
///
/// Gets or sets the real component of a complex number.
///
///
/// Usage example:
///
///
/// DECLARE @tbl TABLE(Num COMPLEX)
/// INSERT INTO @tbl VALUES('1+5i')
/// UPDATE @tbl SET Num.Real = 5
/// SELECT Num.ToString(), Num.Real FROM @tbl
/// -- Returns [Num: 5+5i, Real: 5]
///
///
public double Real
{
get
{
return m_real;
}
set
{
m_real = value;
}
}
///
/// Gets or sets the imaginary component of a complex number.
///
///
/// Usage example:
///
///
/// DECLARE @tbl TABLE(Num COMPLEX)
/// INSERT INTO @tbl VALUES('1+5i')
/// UPDATE @tbl SET Num.Imaginary = 1
/// SELECT Num.ToString(), Num.Imaginary FROM @tbl
/// -- Returns [Num: 1+1i, Imaginary: 1]
///
///
public double Imaginary
{
get
{
return m_imaginary;
}
set
{
m_imaginary = value;
}
}
///
/// Gets or sets the magnitude of a complex number.
///
///
/// Usage example:
///
///
/// DECLARE @tbl TABLE(Num COMPLEX)
/// INSERT INTO @tbl VALUES('1+5i')
/// UPDATE @tbl SET Num.Magnitude = 5
/// SELECT Num.ToString(), Num.Magnitude FROM @tbl
/// -- Returns [Num: 0.98058+4.9029i, Magnitude: 5]
///
///
public double Magnitude
{
get
{
return Math.Sqrt(m_real * m_real + m_imaginary * m_imaginary);
}
set
{
double angle = Angle;
m_real = value * Math.Cos(angle);
m_imaginary = value * Math.Sin(angle);
}
}
///
/// Gets or sets the angle of a complex number.
///
///
/// Usage example:
///
///
/// DECLARE @tbl TABLE(Num COMPLEX)
/// INSERT INTO @tbl VALUES('1+5i')
/// UPDATE @tbl SET Num.Angle = 1
/// SELECT Num.ToString(), Num.Angle FROM @tbl
/// -- Returns [Num: 2.75501+4.29068i, Angle: 1]
///
///
public double Angle
{
get
{
return Math.Atan2(m_imaginary, m_real);
}
set
{
double magnitude = Magnitude;
m_real = magnitude * Math.Cos(value);
m_imaginary = magnitude * Math.Sin(value);
}
}
#endregion
#region [ Methods ]
///
/// Returns the complex conjugate of a complex number.
///
/// The complex conjugate.
///
/// Usage example:
///
///
/// DECLARE @complex COMPLEX = '1+5i'
/// SELECT @complex.Conjugate().ToString()
/// -- Returns 1-5i
///
///
[SqlMethod(OnNullCall = false, IsDeterministic = true)]
public ComplexNumber Conjugate()
{
ComplexNumber num = new ComplexNumber();
num.m_real = m_real;
num.m_imaginary = -m_imaginary;
return num;
}
///
/// Returns the negative value of the complex number.
///
/// The negative value.
///
/// Usage example:
///
///
/// DECLARE @complex COMPLEX = '1+5i'
/// SELECT @complex.Negate().ToString()
/// -- Returns -1-5i
///
///
[SqlMethod(OnNullCall = false, IsDeterministic = true)]
public ComplexNumber Negate()
{
ComplexNumber num = new ComplexNumber();
num.m_real = -m_real;
num.m_imaginary = -m_imaginary;
return num;
}
///
/// Returns the sum of two complex numbers.
///
/// The number to be added to this one.
/// The sum.
///
/// Usage example:
///
///
/// DECLARE @complex COMPLEX = '1+5i'
/// SELECT @complex.[Add]('-5+1i').ToString()
/// -- Returns -4+6i
///
///
[SqlMethod(OnNullCall = false, IsDeterministic = true)]
public ComplexNumber Add(ComplexNumber num)
{
ComplexNumber result = new ComplexNumber();
result.m_real = m_real + num.m_real;
result.m_imaginary = m_imaginary + num.m_imaginary;
return result;
}
///
/// Returns the difference of two complex numbers.
///
/// The number to be subtracted from this one.
/// The difference.
///
/// Usage example:
///
///
/// DECLARE @complex COMPLEX = '1+5i'
/// SELECT @complex.Subtract('-5+1i').ToString()
/// -- Returns 6+4i
///
///
[SqlMethod(OnNullCall = false, IsDeterministic = true)]
public ComplexNumber Subtract(ComplexNumber num)
{
ComplexNumber result = new ComplexNumber();
result.m_real = m_real - num.m_real;
result.m_imaginary = m_imaginary - num.m_imaginary;
return result;
}
///
/// Returns the product of two complex numbers.
///
/// The number to be multiplied with this one.
/// The product.
///
/// Usage example:
///
///
/// DECLARE @complex COMPLEX = '1+5i'
/// SELECT @complex.Multiply('-5+1i').ToString()
/// -- Returns -10-24i
///
///
[SqlMethod(OnNullCall = false, IsDeterministic = true)]
public ComplexNumber Multiply(ComplexNumber num)
{
ComplexNumber result = new ComplexNumber();
result.SetPhasor(Magnitude * num.Magnitude, Angle + num.Angle);
return result;
}
///
/// Returns the quotient of two complex numbers.
///
/// The number to divide by this one.
/// The quotient.
///
/// Usage example:
///
///
/// DECLARE @complex COMPLEX = '1+5i'
/// SELECT @complex.Divide('-5+1i').ToString()
/// -- Returns -1i
///
///
[SqlMethod(OnNullCall = false, IsDeterministic = true)]
public ComplexNumber Divide(ComplexNumber num)
{
ComplexNumber result = new ComplexNumber();
result.SetPhasor(Magnitude / num.Magnitude, Angle - num.Angle);
return result;
}
///
/// Sets the real and imaginary components of the complex number.
///
/// The real component.
/// The imaginary component.
///
/// Usage example:
///
///
/// DECLARE @tbl TABLE(Num COMPLEX)
/// INSERT INTO @tbl VALUES('1+5i')
/// UPDATE @tbl SET Num.SetRect(-5, 1)
/// SELECT Num.ToString() FROM @tbl
/// -- Returns -5+1i
///
///
[SqlMethod(IsMutator = true, OnNullCall = false)]
public void SetRect(double real, double imaginary)
{
m_real = real;
m_imaginary = imaginary;
}
///
/// Sets the magnitude and angle of the vector described by a complex number.
///
/// The magnitude.
/// The angle.
///
/// Usage example:
///
///
/// DECLARE @tbl TABLE(Num COMPLEX)
/// INSERT INTO @tbl VALUES('1+5i')
/// UPDATE @tbl SET Num.SetPhasor(-5, 1)
/// SELECT Num.ToString() FROM @tbl
/// -- Returns -2.70151-4.20735i
///
///
[SqlMethod(IsMutator = true, OnNullCall = false)]
public void SetPhasor(double magnitude, double angle)
{
m_real = magnitude * Math.Cos(angle);
m_imaginary = magnitude * Math.Sin(angle);
}
///
/// Returns the string representation of a complex number.
///
/// The string representation of the complex number.
public override string ToString()
{
StringBuilder image = new StringBuilder();
double real = Math.Round(m_real, 5);
double imaginary = Math.Round(m_imaginary, 5);
if (real == 0.0D && imaginary == 0.0D)
return "0";
if (real != 0.0D)
image.Append(real);
if (imaginary != 0.0D)
{
image.Append(imaginary > 0.0D ? "+" : "-");
image.Append(Math.Abs(imaginary));
image.Append('i');
}
return image.ToString();
}
#endregion
#region [ Static ]
// Static Properties
///
/// Gets a value that represents a null complex number.
///
public static ComplexNumber Null
{
get
{
ComplexNumber num = new ComplexNumber();
num.m_isNull = true;
return num;
}
}
// Static Methods
///
/// Converts the string representation of a complex number to a complex number.
///
/// The string representation of a complex number.
/// The complex number.
[SqlMethod(OnNullCall = false)]
public static ComplexNumber Parse(SqlString str)
{
// Regex pattern to match a single number
const string NumberPattern = @"(?[+-]?(?:[ij][0-9eE\.]+|[0-9eE\.]+[ij]?))";
// Regex pattern to match the operator
const string OperatorPattern = @"(?[+-])";
// Regex pattern to match the whole complex number
//
const string Pattern = @"^" + // Start of string
@"\s*" + // Whitespace
NumberPattern + // Number
@"\s*" + // Whitespace
"(?:" + // Start of optional of noncapturing group
OperatorPattern + // Operator
@"\s*" + // Whitespace
NumberPattern + // Number
@"\s*" + // Whitespace
")?" + // End of optional noncapturing group
"$"; // End of string
Match match;
CaptureCollection numberCaptures;
CaptureCollection operatorCaptures;
double op = 1.0;
double real = 0.0D;
double imaginary = 0.0D;
// Parses the string as a double by first removing the i or j
Func parse = s => double.Parse(Regex.Replace(s, "[ij]", ""));
Func isImaginary = s => Regex.IsMatch(s, "[ij]");
ComplexNumber num;
if (str.IsNull)
return Null;
match = Regex.Match(str.Value, Pattern);
// String format is invalid if regex does not match
if (!match.Success)
throw new FormatException("Input string was not in a correct format.");
// Get the captures for the Number and Operator groups
numberCaptures = match.Groups["Number"].Captures;
operatorCaptures = match.Groups["Operator"].Captures;
// If the string defines two numbers, ensure that exactly one of them is imaginary
if (numberCaptures.Count == 2 && !(isImaginary(numberCaptures[0].Value) ^ isImaginary(numberCaptures[1].Value)))
throw new FormatException("Input string was not in a correct format.");
// Parse the first capture group to the
// appropriate part of the complex number
if (isImaginary(numberCaptures[0].Value))
imaginary = parse(numberCaptures[0].Value);
else
real = parse(numberCaptures[0].Value);
if (numberCaptures.Count == 2)
{
// Determine if the sign needs to be
// inverted based on the operator
if (operatorCaptures[0].Value == "-")
op = -1.0;
// Parse the second capture group to the
// appropriate part of the complex number
if (isImaginary(numberCaptures[1].Value))
imaginary = op * parse(numberCaptures[1].Value);
else
real = op * parse(numberCaptures[1].Value);
}
// Return the complex number
num = new ComplexNumber();
num.m_real = real;
num.m_imaginary = imaginary;
return num;
}
#endregion
}
}