diff --git a/.gitignore b/.gitignore index b011f884..dedb186c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,6 @@ /src/Mathematics.NET.DevApp/*.cs *.runsettings - # Temporary -/src/Mathematics.NET.SourceGenerators/ - # User-specific files *.rsuser *.suo diff --git a/README.md b/README.md index b59279e3..f43e657e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -

Mathematics.NET

+# Mathematics.NET -

Mathematics.NET is a C# class library that provides tools for solving mathematical problems.

+Mathematics.NET is a C# class library that provides tools for solving mathematical problems. [![GitHub](https://img.shields.io/github/license/HamletTanyavong/Mathematics.NET?style=flat-square&logo=github&labelColor=87cefa&color=ffd700)](https://github.com/HamletTanyavong/Mathematics.NET) [![GitHub Repo Stars](https://img.shields.io/github/stars/HamletTanyavong/Mathematics.NET?color=87cefa&style=flat-square&logo=github)](https://github.com/HamletTanyavong/Mathematics.NET/stargazers) diff --git a/docs/articles/fundamentals/numeric-types.md b/docs/articles/fundamentals/numeric-types.md index e36e8ac6..a6d0293b 100644 --- a/docs/articles/fundamentals/numeric-types.md +++ b/docs/articles/fundamentals/numeric-types.md @@ -2,21 +2,21 @@ There are three numeric types that can be used to represent complex, real, and rational numbers in Mathematics.NET. -All Mathematics.NET numbers implement the [IComplex](https://mathematics.hamlettanyavong.com/api/Mathematics.NET.Core.IComplex-2.html) interface. Particularly useful is the fact that, unlike .NET runtime's [INumberBase\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs), `IComplex` defines the `Conjugate` method; this is incredibly helpful in avoiding code duplication for calculations involving complex and real numbers. +All Mathematics.NET numbers implement the [IComplex\](https://mathematics.hamlettanyavong.com/api/Mathematics.NET.Core.IComplex-1.html) interface. Particularly useful is the fact that, unlike .NET runtime's [INumberBase\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs), `IComplex` defines the `Conjugate` method; this is helpful in avoiding code duplication for calculations involving complex and real numbers. ## Floating-Point Types -Floating-point Mathematics.NET numbers may be backed any number that implements [IFloatingPointIee754\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs) and [IMinMaxValue\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs), or more specifically, [float](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Single.cs), [double](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Double.cs), and [decimal](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Decimal.cs). +Floating-point Mathematics.NET numbers are backed by the [double](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Double.cs) numeric type. ### Complex Numbers -To create a complex number, we choose a backing type, in this case `double`, and write +To create a complex number, we can write ```csharp -Complex z = new(3, 4); +ComplexNumber z = new(3, 4); ``` This represents the number $ z = 3+i4 $. We can also specify only one number to create a complex number with no imaginary part ```csharp -Complex z = 3; +ComplexNumber z = 3; ``` which represents $ z = 3 $. @@ -24,15 +24,12 @@ which represents $ z = 3 $. Likewise, to create a real number, write ```csharp -Real z = 1; +Real x = 1.23; ``` -With real numbers, we can also get maximum and minimum values which will depend on the backing type. +To get is backing value, write ```csharp -Console.WriteLine("Max value with float backing type: {0}", Real.MaxValue); -Console.WriteLine("Max value with double backing type: {0}", Real.MaxValue); +double backingValue = x.Value; ``` -This will output `Max value with float backing type: 3.4028235E+38` -and `Max value with double backing type: 1.7976931348623157E+308`. ## Binary Types @@ -40,29 +37,22 @@ Rational numbers are the only Mathematics.NET type in this category. ### Rational Numbers -Rational numbers require two backing types, one that implements [IBinaryInteger\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs) and one that implements both [IFloatingPointIee754\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/IFloatingPointIeee754.cs) and [IMinMaxValue\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/IMinMaxValue.cs). +Rational numbers require a type parameter that implements [IBinaryInteger\](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs). The type specified here is used to represent the numerator and denominator of the rational number. With this information, we can create the following rational numbers: ```csharp -Rational a = 2; -Rational b = new(2, 3); -Rational c = new(3, 4); +Rational a = 2; +Rational b = new(2, 3); +Rational c = new(3, 4); ``` which represent $ a = 2 $, $ b = 2/3 $, and $ c = 3/4 $. -The first type parameter indicates that the constructor only accepts values of that type. In these cases, `a` must be an int, `b` must be a byte, and `c` must be a BigInteger. The second parameter indicates the desired floating-point type with which we want to represent the rational number. We can get this value in two ways, e.g. -```csharp -Console.WriteLine(b.Value); -Console.WriteLine((float)b); -``` -which will both output `0.6666667`. - > [!CAUTION] > The floating-point representation of rational numbers may not be accurate in all cases. -We can also convert a floating-point number into a rational number with an explicit cast +We can also convert a double into a rational number with an explicit cast ```csharp -Console.WriteLine((Rational)3.14); +Console.WriteLine((Rational)3.14); ``` > [!NOTE] > The conversion conversion is not guaranteed to create the "best" fraction; for instance, the value $ 0.3333333333333333 $ will not produce $ 1/3 $ but instead produce $ 8333333333333331 / 25000000000000000 $. diff --git a/docs/articles/get_started/installation.md b/docs/articles/get_started/installation.md index 8487e6ec..39de7618 100644 --- a/docs/articles/get_started/installation.md +++ b/docs/articles/get_started/installation.md @@ -4,7 +4,7 @@ Mathematics.NET is available to download from [nuget](https://www.nuget.org/pack # [Package Reference](#tab/package-reference) -To use Mathematics.NET in you project, add the following line to your .csproj file: +To use Mathematics.NET in your project, add the following line to your .csproj file: ```xml ``` diff --git a/src/Mathematics.NET.DevApp/Mathematics.NET.DevApp.csproj b/src/Mathematics.NET.DevApp/Mathematics.NET.DevApp.csproj index 10014264..db52578b 100644 --- a/src/Mathematics.NET.DevApp/Mathematics.NET.DevApp.csproj +++ b/src/Mathematics.NET.DevApp/Mathematics.NET.DevApp.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Mathematics.NET.SourceGenerators/Extensions.cs b/src/Mathematics.NET.SourceGenerators/Extensions.cs new file mode 100644 index 00000000..cbd8e6a4 --- /dev/null +++ b/src/Mathematics.NET.SourceGenerators/Extensions.cs @@ -0,0 +1,59 @@ +// +// Mathematics.NET +// https://github.com/HamletTanyavong/Mathematics.NET +// +// MIT License +// +// Copyright (c) 2023 Hamlet Tanyavong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +namespace Mathematics.NET.SourceGenerators; + +public static class Extensions +{ + public static string? GetValue(this NameSyntax name) + { + return name switch + { + SimpleNameSyntax simpleNameSyntax => simpleNameSyntax.Identifier.Text, + QualifiedNameSyntax qualifiedNameSyntax => qualifiedNameSyntax.Right.Identifier.Text, + _ => null + }; + } + + // TODO: Determine if every equation should/will have an attribute to remove. + public static MethodDeclarationSyntax RemoveEquationAttribute(this MethodDeclarationSyntax methodDeclarationSyntax) + { + var equationAttributeNode = methodDeclarationSyntax + .DescendantNodes() + .OfType() + .First(syntax => syntax.Name.GetValue() is "Equation" or "EquationAttribute"); + + if (equationAttributeNode.Parent!.ChildNodes().Count() > 1) + { + return methodDeclarationSyntax.RemoveNode(equationAttributeNode, SyntaxRemoveOptions.KeepNoTrivia)!; + } + else + { + return methodDeclarationSyntax.RemoveNode(equationAttributeNode.Parent, SyntaxRemoveOptions.KeepNoTrivia)!; + } + } +} diff --git a/src/Mathematics.NET.SourceGenerators/IncrementalGenerators/DerivativeGenerator.cs b/src/Mathematics.NET.SourceGenerators/IncrementalGenerators/DerivativeGenerator.cs new file mode 100644 index 00000000..034dfb0d --- /dev/null +++ b/src/Mathematics.NET.SourceGenerators/IncrementalGenerators/DerivativeGenerator.cs @@ -0,0 +1,63 @@ +// +// Mathematics.NET +// https://github.com/HamletTanyavong/Mathematics.NET +// +// MIT License +// +// Copyright (c) 2023 Hamlet Tanyavong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +using System.Collections.Immutable; +using System.Text; +using Mathematics.NET.SourceGenerators.Models; +using Mathematics.NET.SourceGenerators.SourceBuilders; + +namespace Mathematics.NET.SourceGenerators.IncrementalGenerators; + +/// A generator for calculating derivatives +[Generator] +public sealed class DerivativeGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var provider = context.SyntaxProvider + .CreateSyntaxProvider(CouldBeEquationAttribute, GetEquationOrNull) + .Where(info => info is not null); + var compilation = context.CompilationProvider.Combine(provider.Collect()); + context.RegisterSourceOutput(compilation, (context, source) => GenerateCode(context, source.Left, source.Right!)); + } + + private static bool CouldBeEquationAttribute(SyntaxNode syntaxNode, CancellationToken cancellationToken) + => syntaxNode is AttributeSyntax attributeSyntax && attributeSyntax.Name.GetValue() is "Equation" or "EquationAttribute"; + + private static MethodInformation? GetEquationOrNull(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + // The method syntax will not be null if attribute syntax is not null since the attribute can only be applied to methods. + var attribute = (AttributeSyntax)context.Node; + return new(attribute, (MethodDeclarationSyntax)attribute.Parent!.Parent!); + } + + private void GenerateCode(SourceProductionContext context, Compilation compilation, ImmutableArray methodInformation) + { + var derivatives = new DerivativesBuilder(compilation, methodInformation); + context.AddSource("Equations.g.cs", derivatives.GenerateSource().GetText(Encoding.UTF8).ToString()); + } +} diff --git a/src/Mathematics.NET.SourceGenerators/Mathematics.NET.SourceGenerators.csproj b/src/Mathematics.NET.SourceGenerators/Mathematics.NET.SourceGenerators.csproj index 04fc35d2..ea490301 100644 --- a/src/Mathematics.NET.SourceGenerators/Mathematics.NET.SourceGenerators.csproj +++ b/src/Mathematics.NET.SourceGenerators/Mathematics.NET.SourceGenerators.csproj @@ -4,6 +4,7 @@ true false latest + enable netstandard2.0 x64 enable diff --git a/src/Mathematics.NET.SourceGenerators/Models/MethodInformation.cs b/src/Mathematics.NET.SourceGenerators/Models/MethodInformation.cs new file mode 100644 index 00000000..7bbe38ae --- /dev/null +++ b/src/Mathematics.NET.SourceGenerators/Models/MethodInformation.cs @@ -0,0 +1,42 @@ +// +// Mathematics.NET +// https://github.com/HamletTanyavong/Mathematics.NET +// +// MIT License +// +// Copyright (c) 2023 Hamlet Tanyavong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +namespace Mathematics.NET.SourceGenerators.Models; + +/// A class containing information about a specific method +public sealed class MethodInformation +{ + public MethodInformation(AttributeSyntax attributeSyntax, MethodDeclarationSyntax methodDeclaration) + { + AttributeSyntax = attributeSyntax; + MethodDeclaration = methodDeclaration; + } + + public AttributeSyntax AttributeSyntax { get; } + + public MethodDeclarationSyntax MethodDeclaration { get; } +} diff --git a/src/Mathematics.NET.SourceGenerators/SourceBuilders/DerivativesBuilder.cs b/src/Mathematics.NET.SourceGenerators/SourceBuilders/DerivativesBuilder.cs new file mode 100644 index 00000000..4ee72477 --- /dev/null +++ b/src/Mathematics.NET.SourceGenerators/SourceBuilders/DerivativesBuilder.cs @@ -0,0 +1,101 @@ +// +// Mathematics.NET +// https://github.com/HamletTanyavong/Mathematics.NET +// +// MIT License +// +// Copyright (c) 2023 Hamlet Tanyavong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +using System.Collections.Immutable; +using Mathematics.NET.SourceGenerators.Models; +using Microsoft.CodeAnalysis.CSharp; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Mathematics.NET.SourceGenerators.SourceBuilders; + +/// Derivatives builder +public sealed class DerivativesBuilder +{ + private readonly string _assemblyName; + private readonly ImmutableArray _methodInformation; + + public DerivativesBuilder(Compilation compilationUnitSyntax, ImmutableArray methodInformation) + { + _assemblyName = compilationUnitSyntax.AssemblyName!; + _methodInformation = methodInformation; + } + + public CompilationUnitSyntax GenerateSource() + { + var members = GenerateMembers(); + return CreateCompilationUnit(members); + } + + private CompilationUnitSyntax CreateCompilationUnit(MemberDeclarationSyntax[] memberDeclarations) + { + return CompilationUnit() + .WithUsings( + SingletonList( + UsingDirective( + QualifiedName( + QualifiedName( + IdentifierName("Mathematics"), + IdentifierName("NET")), + IdentifierName("Core"))) + .WithUsingKeyword( + Token( + TriviaList( + Comment("// Auto-generated code")), + SyntaxKind.UsingKeyword, + TriviaList())))) + .WithMembers( + SingletonList( + FileScopedNamespaceDeclaration( + QualifiedName( + QualifiedName( + IdentifierName(_assemblyName), + IdentifierName("Generated")), + IdentifierName("Mathematics"))) + .WithMembers( + SingletonList( + ClassDeclaration("Equations") + .WithModifiers( + TokenList(new[] { + Token(SyntaxKind.PublicKeyword), + Token(SyntaxKind.StaticKeyword) })) + .WithMembers( + List(memberDeclarations)))))) + .NormalizeWhitespace(); + } + + private MemberDeclarationSyntax[] GenerateMembers() + { + var result = new MemberDeclarationSyntax[_methodInformation.Length]; + for (int i = 0; i < _methodInformation.Length; i++) + { + var equation = _methodInformation[i].MethodDeclaration.RemoveEquationAttribute(); + var transformedEquation = SymbolicsHelper.TransformEquation(equation); + result[i] = transformedEquation; + } + return result; + } +} diff --git a/src/Mathematics.NET.SourceGenerators/SymbolicsHelper.cs b/src/Mathematics.NET.SourceGenerators/SymbolicsHelper.cs new file mode 100644 index 00000000..425f463b --- /dev/null +++ b/src/Mathematics.NET.SourceGenerators/SymbolicsHelper.cs @@ -0,0 +1,123 @@ +// +// Mathematics.NET +// https://github.com/HamletTanyavong/Mathematics.NET +// +// MIT License +// +// Copyright (c) 2023 Hamlet Tanyavong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +using Microsoft.CodeAnalysis.CSharp; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Mathematics.NET.SourceGenerators; + +public static class SymbolicsHelper +{ + // TODO: Reduce mathematical expressions after taking derivatives + public static MethodDeclarationSyntax TransformEquation(MethodDeclarationSyntax methodDeclarationSyntax) + { + MethodDeclarationSyntax transformation = methodDeclarationSyntax; + while (CanTakeInnerDerivatives(transformation, out var derivativeExpressions)) + { + foreach (var derivativeExpression in derivativeExpressions) + { + var flattenedDerivativeExpression = ComputeDerivative(derivativeExpression); + transformation = transformation.ReplaceNode(derivativeExpression.Parent!, flattenedDerivativeExpression ?? derivativeExpression); + } + } + return transformation; + } + + // We want to resolve the innermost derivative first, so we choose the derivative + // expression with no other derivative expressions present in its descendant nodes. + private static bool CanTakeInnerDerivatives(MethodDeclarationSyntax syntaxNode, out IEnumerable derivatives) + { + var descendantNodes = syntaxNode.DescendantNodes(); + derivatives = descendantNodes + .OfType() + .Where(x => x.Name.Identifier.ValueText == "Dif" && !x + .DescendantNodes() + .OfType() + .Any(x => x.Name.Identifier.ValueText == "Dif")); + return derivatives.Count() > 0; + } + + // TODO: Account for higher order derivatives + // TODO: Take into account differentiating with respect to multiple variables + private static ExpressionSyntax? ComputeDerivative(MemberAccessExpressionSyntax memberAccessExpressionSyntax) + { + // memberAccessExpressionSyntax is the Dif expression extracted + var invocationExpression = (InvocationExpressionSyntax)memberAccessExpressionSyntax.Parent!; + + var derivativeExpression = (InvocationExpressionSyntax)GetDifferentiableExpression(invocationExpression.ArgumentList.Arguments[0])!; + var method = (MemberAccessExpressionSyntax)derivativeExpression.Expression; + var args = derivativeExpression.ArgumentList; + return method.Name.Identifier.ValueText switch + { + "Cos" => DifCos(method, args), + "Sin" => DifSin(method, args), + _ => null + }; + } + + private static ExpressionSyntax? GetDifferentiableExpression(ArgumentSyntax argumentSyntax) + { + return argumentSyntax.Expression switch + { + SimpleLambdaExpressionSyntax simpleLambdaExpressionSyntax => simpleLambdaExpressionSyntax.ExpressionBody, + _ => null + }; + } + + // Mathematical functions + + private static ExpressionSyntax DifCos(MemberAccessExpressionSyntax memberAccessExpressionSyntax, ArgumentListSyntax argumentListSyntax) + { + var cosNameSyntax = memberAccessExpressionSyntax + .DescendantNodes() + .OfType() + .First(x => x.Identifier.ValueText == "Cos"); + var newExpression = memberAccessExpressionSyntax.ReplaceNode(cosNameSyntax, IdentifierName("Sin")); + return BinaryExpression( + SyntaxKind.MultiplyExpression, + PrefixUnaryExpression( + SyntaxKind.UnaryMinusExpression, + LiteralExpression( + SyntaxKind.NumericLiteralExpression, + Literal(1))), + InvocationExpression( + newExpression, + argumentListSyntax)); + } + + private static ExpressionSyntax DifSin(MemberAccessExpressionSyntax memberAccessExpressionSyntax, ArgumentListSyntax argumentListSyntax) + { + var sinNameSyntax = memberAccessExpressionSyntax + .DescendantNodes() + .OfType() + .First(x => x.Identifier.ValueText == "Sin"); + var newExpression = memberAccessExpressionSyntax.ReplaceNode(sinNameSyntax, IdentifierName("Cos")); + return InvocationExpression( + newExpression, + argumentListSyntax); + } +} diff --git a/src/Mathematics.NET/Core/Complex.cs b/src/Mathematics.NET/Core/Complex.cs deleted file mode 100644 index f7474e9d..00000000 --- a/src/Mathematics.NET/Core/Complex.cs +++ /dev/null @@ -1,674 +0,0 @@ -// -// Mathematics.NET -// https://github.com/HamletTanyavong/Mathematics.NET -// -// MIT License -// -// Copyright (c) 2023 Hamlet Tanyavong -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Mathematics.NET.Core; - -/// Represents a complex number -/// A type that implements and -[Serializable] -[StructLayout(LayoutKind.Sequential)] -public readonly struct Complex - : IComplex, T>, - IDifferentiableFunctions, T> - where T : IFloatingPointIeee754, IMinMaxValue -{ - private static readonly Complex s_im = new(Real.Zero, Real.One); - private static readonly Complex s_imOverTwo = new(Real.Zero, Real.One / Real.Two); - - private static readonly Complex s_oneOverTwo = T.CreateTruncating(0.5); - private static readonly Complex s_three = T.CreateTruncating(3.0); - private static readonly Real s_four = T.CreateTruncating(4.0); - - // For computing Asin and Acos - private static readonly Real s_asinOverflowThreshold = Real.Sqrt(Real.MaxValue) / Real.Two; - private static readonly Real s_threeOverFour = T.CreateTruncating(0.75); - private static readonly Real s_threeOverTwo = T.CreateTruncating(1.5); - - public static readonly Complex Zero = Real.Zero; - public static readonly Complex One = Real.One; - public static readonly Complex Two = Real.Two; - - public static readonly Complex NaN = new(Real.NaN, Real.NaN); - public static readonly Complex Infinity = new(Real.PositiveInfinity, Real.PositiveInfinity); - - private readonly Real _real; - private readonly Real _imaginary; - - public Complex(Real real) - { - _real = real; - _imaginary = T.Zero; - } - - public Complex(Real real, Real imaginary) - { - _real = real; - _imaginary = imaginary; - } - - // - // Complex number properties - // - - public Real Re => _real; - public Real Im => _imaginary; - - public Real Magnitude => Hypot(_real.Value, _imaginary.Value); - public Real Phase => T.Atan2(_imaginary.Value, _real.Value); - - // - // Constants - // - - static Complex IComplex, T>.Zero => Zero; - static Complex IComplex, T>.One => One; - static Complex IComplex, T>.Two => Two; - static Complex IComplex, T>.NaN => NaN; - - // - // Operators - // - - public static Complex operator -(Complex z) => new(-z._real, -z._imaginary); - - public static Complex operator +(Complex z, Complex w) => new(z._real + w._real, z._imaginary + w._imaginary); - - public static Complex operator -(Complex z, Complex w) => new(z._real - w._real, z._imaginary - w._imaginary); - - public static Complex operator *(Complex z, Complex w) - => new(z._real * w._real - z._imaginary * w._imaginary, z._real * w._imaginary + w._real * z._imaginary); - - // From Michael Baudin and Robert L. Smith - public static Complex operator /(Complex z, Complex w) - { - var zRe = z._real.Value; - var zIm = z._imaginary.Value; - var wRe = w._real.Value; - var wIm = w._imaginary.Value; - - T reResult; - T imResult; - if (T.Abs(wIm) <= T.Abs(wRe)) - { - DivisionHelper(zRe, zIm, wRe, wIm, out reResult, out imResult); - } - else - { - DivisionHelper(zIm, zRe, wIm, wRe, out reResult, out imResult); - imResult = -imResult; - } - - return new(reResult, imResult); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DivisionHelper(T x, T y, T maxW, T minW, out T real, out T imaginary) - { - var ratio = minW / maxW; - var scale = T.One / (maxW + minW * ratio); - if (ratio != T.Zero) - { - real = (x + y * ratio) * scale; - imaginary = (y - x * ratio) * scale; - } - else - { - real = (x + minW * (y / maxW)) * scale; - imaginary = (y - minW * (x / maxW)) * scale; - } - } - - // - // Equality - // - - public static bool operator ==(Complex left, Complex right) => left.Re == right.Re && left.Im == right.Im; - - public static bool operator !=(Complex left, Complex right) => left.Re != right.Re || left.Im != right.Im; - - public override bool Equals([NotNullWhen(true)] object? obj) => obj is Complex other && Equals(other); - - public bool Equals(Complex value) => _real.Equals(value.Re) && _imaginary.Equals(value.Im); - - public override int GetHashCode() => HashCode.Combine(_real, _imaginary); - - // - // Formatting - // - - public override string ToString() => ToString(null, null); - - public string ToString(string? format, IFormatProvider? provider) - { - format = string.IsNullOrEmpty(format) ? "ALL" : format.ToUpperInvariant(); - provider ??= NumberFormatInfo.InvariantInfo; - - if (format is "ALL") - { - return string.Format(provider, "({0}, {1})", _real.ToString(null, provider), _imaginary.ToString(null, provider)); - } - else if (format is "RE") - { - return string.Format(provider, "{0}", _real.ToString(null, provider)); - } - else if (format is "IM") - { - return string.Format(provider, "{0}", _imaginary.ToString(null, provider)); - } - else - { - throw new FormatException($"The \"{format}\" format is not supported."); - } - } - - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) - { - format = format.IsEmpty ? "ALL" : format.ToString().ToUpperInvariant(); - provider ??= NumberFormatInfo.InvariantInfo; - - if (format is "ALL") - { - // There are a minimum of 6 characters for "(0, 0)". - int charsCurrentlyWritten = 0; - if (destination.Length < 6) - { - charsWritten = charsCurrentlyWritten; - return false; - } - - destination[charsCurrentlyWritten++] = '('; - - bool tryFormatSucceeded = _real.TryFormat(destination[charsCurrentlyWritten..], out int tryFormatCharsWritten, null, provider); - charsCurrentlyWritten += tryFormatCharsWritten; - if (!tryFormatSucceeded || destination.Length < charsCurrentlyWritten + 1) - { - charsWritten = charsCurrentlyWritten; - return false; - } - - destination[charsCurrentlyWritten++] = ','; - if (destination.Length < charsCurrentlyWritten + 1) - { - charsWritten = charsCurrentlyWritten; - return false; - } - destination[charsCurrentlyWritten++] = ' '; - if (destination.Length < charsCurrentlyWritten + 1) - { - charsWritten = charsCurrentlyWritten; - return false; - } - - tryFormatSucceeded = _imaginary.TryFormat(destination[charsCurrentlyWritten..], out tryFormatCharsWritten, null, provider); - charsCurrentlyWritten += tryFormatCharsWritten; - if (!tryFormatSucceeded || destination.Length < charsCurrentlyWritten + 1) - { - charsWritten = charsCurrentlyWritten; - return false; - } - - destination[charsCurrentlyWritten++] = ')'; - - charsWritten = charsCurrentlyWritten; - return true; - } - else if (format is "RE") - { - return _real.TryFormat(destination, out charsWritten, null, provider); - } - else if (format is "IM") - { - return _imaginary.TryFormat(destination, out charsWritten, null, provider); - } - else - { - throw new FormatException($"The \"{format}\" format is not supported."); - } - } - - // - // Parsing - // - - public static Complex Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); - - public static Complex Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); - - public static Complex Parse(string s, NumberStyles style, IFormatProvider? provider) - { - ArgumentNullException.ThrowIfNull(s); - return Parse((ReadOnlySpan)s, style, provider); - } - - public static Complex Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) - { - if (!TryParse(s, style, provider, out Complex result)) - { - return Infinity; - } - return result; - } - - public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Complex result) - => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); - - public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Complex result) - => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); - - public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Complex result) - => TryParse((ReadOnlySpan)s, style, provider, out result); - - public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Complex result) - { - s = s.Trim(); - int openParenthesis = s.IndexOf('('); - int split = s.IndexOf(','); - int closeParenthesis = s.IndexOf(')'); - - // There a minimum of 5 characters for "(0,0)". - if (s.Length < 5 || openParenthesis == -1 || split == -1 || closeParenthesis == -1 || openParenthesis > split || openParenthesis > closeParenthesis || split > closeParenthesis) - { - result = Zero; - return false; - } - - if (!Real.TryParse(s.Slice(openParenthesis + 1, split - 1), style, provider, out Real real)) - { - result = Zero; - return false; - } - - if (!Real.TryParse(s.Slice(split + 1, closeParenthesis - split - 1), style, provider, out Real imaginary)) - { - result = Zero; - return false; - } - - result = new(real, imaginary); - return true; - } - - // - // Methods - // - - public static Complex Abs(Complex z) => Hypot(z._real.Value, z._imaginary.Value); - - public static Complex Conjugate(Complex z) => new(z._real, -z._imaginary); - - public static Complex FromPolarForm(Real magnitude, Real phase) - => new(magnitude * T.Cos(phase.Value), magnitude * T.Sin(phase.Value)); - - private static T Hypot(T x, T y) - { - // Factor out the larger value to avoid possible overflow - x = T.Abs(x); - y = T.Abs(y); - - T small, large; - if (x < y) - { - small = x; - large = y; - } - else - { - small = y; - large = x; - } - - if (small == T.Zero) - { - return large; - } - else if (T.IsPositiveInfinity(large) && !T.IsNaN(small)) - { - return T.PositiveInfinity; - } - else - { - T ratio = small / large; - return large * T.Sqrt(T.One + ratio * ratio); - } - } - - public static bool IsFinite(Complex z) => Real.IsFinite(z._real) && Real.IsFinite(z._imaginary); - - public static bool IsInfinity(Complex z) => Real.IsInfinity(z._real) || Real.IsInfinity(z._imaginary); - - public static bool IsNaN(Complex z) => !IsInfinity(z) && !IsFinite(z); - - public static bool IsZero(Complex z) => Real.IsZero(z._real) && Real.IsZero(z._imaginary); - - public static Complex Reciprocate(Complex z) - { - if (z._real == T.Zero && z._imaginary == T.Zero) - { - return Infinity; - } - - var re = z._real.Value; - var im = z._imaginary.Value; - - T reResult; - T imResult; - if (T.Abs(im) <= T.Abs(re)) - { - DivisionHelper(T.One, T.Zero, re, im, out reResult, out imResult); - } - else - { - DivisionHelper(T.Zero, T.One, im, re, out reResult, out imResult); - imResult = -imResult; - } - - return new(reResult, imResult); - } - - // We will only consider the real part of complex numbers for these conversions. - - public static bool TryConvertFromChecked(V value, out Complex result) - where V : INumberBase - { - result = T.CreateChecked(value); - return true; - } - - public static bool TryConvertFromSaturating(V value, out Complex result) - where V : INumberBase - { - result = T.CreateSaturating(value); - return true; - } - - public static bool TryConvertFromTruncating(V value, out Complex result) - where V : INumberBase - { - result = T.CreateTruncating(value); - return true; - } - - public static bool TryConvertToChecked(Complex value, [MaybeNullWhen(false)] out V result) - where V : INumberBase - { - if (value._imaginary == Real.Zero) - { - throw new OverflowException(); - } - - result = V.CreateChecked(value._real.Value); - return true; - } - - public static bool TryConvertToSaturating(Complex value, [MaybeNullWhen(false)] out V result) - where V : INumberBase - { - result = V.CreateSaturating(value._real.Value); - return true; - } - - public static bool TryConvertToTruncating(Complex value, [MaybeNullWhen(false)] out V result) - where V : INumberBase - { - result = V.CreateTruncating(value._real.Value); - return true; - } - - // - // IDifferentiableFunctions interface - // - - // Exponential functions - - public static Complex Exp(Complex z) - { - Real expReal = Real.Exp(z._real); - return new(expReal * Real.Cos(z._imaginary), expReal * Real.Sin(z._imaginary)); - } - - public static Complex Exp2(Complex z) => Exp(Real.Ln2 * z); - - public static Complex Exp10(Complex z) => Exp(Real.Ln10 * z); - - // Hyperbolic functions - - public static Complex Acosh(Complex z) => Ln(z + Sqrt(z * z - One)); - - public static Complex Asinh(Complex z) => Ln(z + Sqrt(z * z + One)); - - public static Complex Atanh(Complex z) => s_oneOverTwo * Ln((One + z) / (One - z)); - - public static Complex Cosh(Complex z) - => new(Real.Cosh(z._real) * Real.Cos(z._imaginary), Real.Sinh(z._real) * Real.Sin(z._imaginary)); - - public static Complex Sinh(Complex z) - => new(Real.Sinh(z._real) * Real.Cos(z._imaginary), Real.Cosh(z._real) * Real.Sin(z._imaginary)); - - public static Complex Tanh(Complex z) => Sinh(z) / Cosh(z); - - // Logarithmic functions - - public static Complex Ln(Complex z) => new(Real.Ln(Hypot(z._real.Value, z._imaginary.Value)), Real.Atan2(z._imaginary, z._real)); - - public static Complex Log(Complex z, Complex b) => Ln(z) / Ln(b); - - public static Complex Log2(Complex z) => Ln(z) / Ln(Real.Ln2); - - public static Complex Log10(Complex z) => Ln(z) / Ln(Real.Ln10); - - // Power functions - - public static Complex Pow(Complex z, Complex w) => Exp(w * Ln(z)); - - // Root functions - - public static Complex Cbrt(Complex z) => Exp(Ln(z) / s_three); - - public static Complex Root(Complex z, Complex w) => Exp(Ln(z) / w); - - public static Complex Sqrt(Complex z) => Exp(s_oneOverTwo * Ln(z)); - - // Trigonometric functions - - public static Complex Acos(Complex z) - { - AsinInternal(Real.Abs(z._real), Real.Abs(z._imaginary), out Real b, out Real bPrime, out Real v); - - Real u; - if (bPrime < Real.Zero) - { - u = Real.Acos(b); - } - else - { - u = Real.Atan(Real.One / bPrime); - } - - if (z._real < Real.Zero) - { - u = Real.Pi - u; - } - if (z._imaginary > Real.Zero) - { - v = -v; - } - - return new(u, v); - } - - public static Complex Asin(Complex z) - { - AsinInternal(Real.Abs(z._real), Real.Abs(z._imaginary), out Real b, out Real bPrime, out Real v); - - Real u; - if (bPrime < Real.Zero) - { - u = Real.Asin(b); - } - else - { - u = Real.Atan(bPrime); - } - - if (z._real < Real.Zero) - { - u = -u; - } - if (z._imaginary < Real.Zero) - { - v = -v; - } - - return new(u, v); - } - - private static void AsinInternal(Real x, Real y, out Real b, out Real bPrime, out Real v) - { - // This is the same method described by Hull, Fairgrieve, and Tang in "Implementing the Complex - // ArcSine and Arccosine Functions Using Exception Handling" that is used in System.Numerics.Complex. - if (x > s_asinOverflowThreshold || y > s_asinOverflowThreshold) - { - b = -Real.One; - bPrime = x / y; - - Real small, big; - if (x < y) - { - small = x; - big = y; - } - else - { - small = y; - big = x; - } - Real ratio = small / big; - v = Real.Ln2 + Real.Ln(big) + Real.Ln(ratio * ratio + Real.One) / Real.Two; - } - else - { - Real r = Hypot((x + Real.One).Value, y.Value); - Real s = Hypot((x - Real.One).Value, y.Value); - - Real a = (r + s) / Real.Two; - b = x / a; - - if (b > s_threeOverFour) - { - if (x <= Real.One) - { - Real amx = (y * y / (r + (x + Real.One)) + (s + (Real.One - x))) / Real.Two; - bPrime = x / Real.Sqrt((a + x) * amx); - } - else - { - Real t = (Real.One / (r + (x + Real.One)) + Real.One / (s + (x - Real.One))) / Real.Two; - bPrime = x / y / Real.Sqrt((a + x) * t); - } - } - else - { - bPrime = -Real.One; - } - - if (a < s_threeOverTwo) - { - if (x < Real.One) - { - Real t = (Real.One / (r + (x + Real.One)) + Real.One / (s + (Real.One - x))) / Real.Two; - Real am1 = y * y * t; - v = Real.Ln(am1 + y * Real.Sqrt(t * (a + Real.One)) + Real.One); - } - else - { - Real am1 = (y * y / (r + (x + Real.One)) + (s + (x - Real.One))) / Real.Two; - v = Real.Ln(am1 + Real.Sqrt(am1 * (a + Real.One)) + Real.One); - } - } - else - { - v = Real.Ln(a + Real.Sqrt((a - Real.One) * (a + Real.One))); - } - } - } - - public static Complex Atan(Complex z) => s_imOverTwo * Ln((s_im + z) / (s_im - z)); - - public static Complex Cos(Complex z) - { - Real p = Real.Exp(z._imaginary); - Real q = Real.One / p; - Real sinh = (p - q) / Real.Two; - Real cosh = (p + q) / Real.Two; - return new(Real.Cos(z._real) * cosh, -Real.Sin(z._real) * sinh); - } - - public static Complex Sin(Complex z) - { - Real p = Real.Exp(z._imaginary); - Real q = Real.One / p; - Real sinh = (p - q) / Real.Two; - Real cosh = (p + q) / Real.Two; - return new(Real.Sin(z._real) * cosh, Real.Cos(z._real) * sinh); - } - - public static Complex Tan(Complex z) - { - Real x2 = Real.Two * z._real; - Real y2 = Real.Two * z._imaginary; - Real p = Real.Exp(y2); - Real q = Real.One / p; - Real cosh = (p + q) / Real.Two; - if (Real.Abs(z._imaginary) <= s_four) - { - Real sinh = (p - q) / Real.Two; - Real D = Real.Cos(x2) + cosh; - return new(Real.Sin(x2) / D, sinh / D); - } - else - { - Real D = Real.One + Real.Cos(x2) / cosh; - return new(Real.Sin(x2) / cosh / D, Real.Tanh(y2) / D); - } - } - - // - // Implicit Operators - // - - public static implicit operator Complex(T x) => new(x); - - /// Convert a value of type to one of type - /// The value to convert - public static implicit operator Complex(Real x) => new(x); -} diff --git a/src/Mathematics.NET/Core/ComplexNumber.cs b/src/Mathematics.NET/Core/ComplexNumber.cs new file mode 100644 index 00000000..35f91d82 --- /dev/null +++ b/src/Mathematics.NET/Core/ComplexNumber.cs @@ -0,0 +1,664 @@ +// +// Mathematics.NET +// https://github.com/HamletTanyavong/Mathematics.NET +// +// MIT License +// +// Copyright (c) 2023 Hamlet Tanyavong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Mathematics.NET.Core; + +/// Represents a complex number +[Serializable] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ComplexNumber + : IComplex, + IDifferentiableFunctions +{ + private static readonly ComplexNumber s_im = new(Real.Zero, Real.One); + private static readonly ComplexNumber s_imOverTwo = new(Real.Zero, Real.One / 2.0); + + // For computing Asin and Acos + private static readonly Real s_asinOverflowThreshold = Real.Sqrt(Real.MaxValue) / 2.0; + + public static readonly ComplexNumber Zero = Real.Zero; + public static readonly ComplexNumber One = Real.One; + + public static readonly ComplexNumber NaN = new(Real.NaN, Real.NaN); + public static readonly ComplexNumber Infinity = new(Real.PositiveInfinity, Real.PositiveInfinity); + + private readonly Real _real; + private readonly Real _imaginary; + + public ComplexNumber(Real real) + { + _real = real; + _imaginary = 0.0; + } + + public ComplexNumber(Real real, Real imaginary) + { + _real = real; + _imaginary = imaginary; + } + + // + // Complex number properties + // + + public Real Re => _real; + public Real Im => _imaginary; + + public Real Magnitude => Hypot(_real.Value, _imaginary.Value); + public Real Phase => Math.Atan2(_imaginary.Value, _real.Value); + + // + // Constants + // + + static ComplexNumber IComplex.Zero => Zero; + static ComplexNumber IComplex.One => One; + static ComplexNumber IComplex.NaN => NaN; + + // + // Operators + // + + public static ComplexNumber operator -(ComplexNumber z) => new(-z._real, -z._imaginary); + + public static ComplexNumber operator +(ComplexNumber z, ComplexNumber w) => new(z._real + w._real, z._imaginary + w._imaginary); + + public static ComplexNumber operator -(ComplexNumber z, ComplexNumber w) => new(z._real - w._real, z._imaginary - w._imaginary); + + public static ComplexNumber operator *(ComplexNumber z, ComplexNumber w) + => new(z._real * w._real - z._imaginary * w._imaginary, z._real * w._imaginary + w._real * z._imaginary); + + // From Michael Baudin and Robert L. Smith + public static ComplexNumber operator /(ComplexNumber z, ComplexNumber w) + { + var zRe = z._real.Value; + var zIm = z._imaginary.Value; + var wRe = w._real.Value; + var wIm = w._imaginary.Value; + + double reResult; + double imResult; + if (Math.Abs(wIm) <= Math.Abs(wRe)) + { + DivisionHelper(zRe, zIm, wRe, wIm, out reResult, out imResult); + } + else + { + DivisionHelper(zIm, zRe, wIm, wRe, out reResult, out imResult); + imResult = -imResult; + } + + return new(reResult, imResult); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DivisionHelper(double x, double y, double maxW, double minW, out double real, out double imaginary) + { + var ratio = minW / maxW; + var scale = 1.0 / (maxW + minW * ratio); + if (ratio != 0.0) + { + real = (x + y * ratio) * scale; + imaginary = (y - x * ratio) * scale; + } + else + { + real = (x + minW * (y / maxW)) * scale; + imaginary = (y - minW * (x / maxW)) * scale; + } + } + + // + // Equality + // + + public static bool operator ==(ComplexNumber left, ComplexNumber right) => left.Re == right.Re && left.Im == right.Im; + + public static bool operator !=(ComplexNumber left, ComplexNumber right) => left.Re != right.Re || left.Im != right.Im; + + public override bool Equals([NotNullWhen(true)] object? obj) => obj is ComplexNumber other && Equals(other); + + public bool Equals(ComplexNumber value) => _real.Equals(value.Re) && _imaginary.Equals(value.Im); + + public override int GetHashCode() => HashCode.Combine(_real, _imaginary); + + // + // Formatting + // + + public override string ToString() => ToString(null, null); + + public string ToString(string? format, IFormatProvider? provider) + { + format = string.IsNullOrEmpty(format) ? "ALL" : format.ToUpperInvariant(); + provider ??= NumberFormatInfo.InvariantInfo; + + if (format is "ALL") + { + return string.Format(provider, "({0}, {1})", _real.ToString(null, provider), _imaginary.ToString(null, provider)); + } + else if (format is "RE") + { + return string.Format(provider, "{0}", _real.ToString(null, provider)); + } + else if (format is "IM") + { + return string.Format(provider, "{0}", _imaginary.ToString(null, provider)); + } + else + { + throw new FormatException($"The \"{format}\" format is not supported."); + } + } + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) + { + format = format.IsEmpty ? "ALL" : format.ToString().ToUpperInvariant(); + provider ??= NumberFormatInfo.InvariantInfo; + + if (format is "ALL") + { + // There are a minimum of 6 characters for "(0, 0)". + int charsCurrentlyWritten = 0; + if (destination.Length < 6) + { + charsWritten = charsCurrentlyWritten; + return false; + } + + destination[charsCurrentlyWritten++] = '('; + + bool tryFormatSucceeded = _real.TryFormat(destination[charsCurrentlyWritten..], out int tryFormatCharsWritten, null, provider); + charsCurrentlyWritten += tryFormatCharsWritten; + if (!tryFormatSucceeded || destination.Length < charsCurrentlyWritten + 1) + { + charsWritten = charsCurrentlyWritten; + return false; + } + + destination[charsCurrentlyWritten++] = ','; + if (destination.Length < charsCurrentlyWritten + 1) + { + charsWritten = charsCurrentlyWritten; + return false; + } + destination[charsCurrentlyWritten++] = ' '; + if (destination.Length < charsCurrentlyWritten + 1) + { + charsWritten = charsCurrentlyWritten; + return false; + } + + tryFormatSucceeded = _imaginary.TryFormat(destination[charsCurrentlyWritten..], out tryFormatCharsWritten, null, provider); + charsCurrentlyWritten += tryFormatCharsWritten; + if (!tryFormatSucceeded || destination.Length < charsCurrentlyWritten + 1) + { + charsWritten = charsCurrentlyWritten; + return false; + } + + destination[charsCurrentlyWritten++] = ')'; + + charsWritten = charsCurrentlyWritten; + return true; + } + else if (format is "RE") + { + return _real.TryFormat(destination, out charsWritten, null, provider); + } + else if (format is "IM") + { + return _imaginary.TryFormat(destination, out charsWritten, null, provider); + } + else + { + throw new FormatException($"The \"{format}\" format is not supported."); + } + } + + // + // Parsing + // + + public static ComplexNumber Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); + + public static ComplexNumber Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); + + public static ComplexNumber Parse(string s, NumberStyles style, IFormatProvider? provider) + { + ArgumentNullException.ThrowIfNull(s); + return Parse((ReadOnlySpan)s, style, provider); + } + + public static ComplexNumber Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + { + if (!TryParse(s, style, provider, out ComplexNumber result)) + { + return Infinity; + } + return result; + } + + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out ComplexNumber result) + => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); + + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out ComplexNumber result) + => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); + + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ComplexNumber result) + => TryParse((ReadOnlySpan)s, style, provider, out result); + + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ComplexNumber result) + { + s = s.Trim(); + int openParenthesis = s.IndexOf('('); + int split = s.IndexOf(','); + int closeParenthesis = s.IndexOf(')'); + + // There a minimum of 5 characters for "(0,0)". + if (s.Length < 5 || openParenthesis == -1 || split == -1 || closeParenthesis == -1 || openParenthesis > split || openParenthesis > closeParenthesis || split > closeParenthesis) + { + result = Zero; + return false; + } + + if (!Real.TryParse(s.Slice(openParenthesis + 1, split - 1), style, provider, out Real real)) + { + result = Zero; + return false; + } + + if (!Real.TryParse(s.Slice(split + 1, closeParenthesis - split - 1), style, provider, out Real imaginary)) + { + result = Zero; + return false; + } + + result = new(real, imaginary); + return true; + } + + // + // Methods + // + + public static ComplexNumber Abs(ComplexNumber z) => Hypot(z._real.Value, z._imaginary.Value); + + public static ComplexNumber Conjugate(ComplexNumber z) => new(z._real, -z._imaginary); + + public static ComplexNumber FromPolarForm(Real magnitude, Real phase) + => new(magnitude * Math.Cos(phase.Value), magnitude * Math.Sin(phase.Value)); + + private static double Hypot(double x, double y) + { + // Factor out the larger value to avoid possible overflow + x = Math.Abs(x); + y = Math.Abs(y); + + double small, large; + if (x < y) + { + small = x; + large = y; + } + else + { + small = y; + large = x; + } + + if (small == 0.0) + { + return large; + } + else if (double.IsPositiveInfinity(large) && !double.IsNaN(small)) + { + return double.PositiveInfinity; + } + else + { + double ratio = small / large; + return large * Math.Sqrt(1.0 + ratio * ratio); + } + } + + public static bool IsFinite(ComplexNumber z) => Real.IsFinite(z._real) && Real.IsFinite(z._imaginary); + + public static bool IsInfinity(ComplexNumber z) => Real.IsInfinity(z._real) || Real.IsInfinity(z._imaginary); + + public static bool IsNaN(ComplexNumber z) => !IsInfinity(z) && !IsFinite(z); + + public static bool IsZero(ComplexNumber z) => Real.IsZero(z._real) && Real.IsZero(z._imaginary); + + public static ComplexNumber Reciprocate(ComplexNumber z) + { + if (z._real == 0.0 && z._imaginary == 0.0) + { + return Infinity; + } + + var re = z._real.Value; + var im = z._imaginary.Value; + + double reResult; + double imResult; + if (Math.Abs(im) <= Math.Abs(re)) + { + DivisionHelper(1.0, 0.0, re, im, out reResult, out imResult); + } + else + { + DivisionHelper(0.0, 1.0, im, re, out reResult, out imResult); + imResult = -imResult; + } + + return new(reResult, imResult); + } + + // We will only consider the real part of complex numbers for these conversions. + + public static bool TryConvertFromChecked(V value, out ComplexNumber result) + where V : INumberBase + { + result = double.CreateChecked(value); + return true; + } + + public static bool TryConvertFromSaturating(V value, out ComplexNumber result) + where V : INumberBase + { + result = double.CreateSaturating(value); + return true; + } + + public static bool TryConvertFromTruncating(V value, out ComplexNumber result) + where V : INumberBase + { + result = double.CreateTruncating(value); + return true; + } + + public static bool TryConvertToChecked(ComplexNumber value, [MaybeNullWhen(false)] out V result) + where V : INumberBase + { + if (value._imaginary == Real.Zero) + { + throw new OverflowException(); + } + + result = V.CreateChecked(value._real.Value); + return true; + } + + public static bool TryConvertToSaturating(ComplexNumber value, [MaybeNullWhen(false)] out V result) + where V : INumberBase + { + result = V.CreateSaturating(value._real.Value); + return true; + } + + public static bool TryConvertToTruncating(ComplexNumber value, [MaybeNullWhen(false)] out V result) + where V : INumberBase + { + result = V.CreateTruncating(value._real.Value); + return true; + } + + // + // IDifferentiableFunctions interface + // + + // Exponential functions + + public static ComplexNumber Exp(ComplexNumber z) + { + Real expReal = Real.Exp(z._real); + return new(expReal * Real.Cos(z._imaginary), expReal * Real.Sin(z._imaginary)); + } + + public static ComplexNumber Exp2(ComplexNumber z) => Exp(Real.Ln2 * z); + + public static ComplexNumber Exp10(ComplexNumber z) => Exp(Real.Ln10 * z); + + // Hyperbolic functions + + public static ComplexNumber Acosh(ComplexNumber z) => Ln(z + Sqrt(z * z - One)); + + public static ComplexNumber Asinh(ComplexNumber z) => Ln(z + Sqrt(z * z + One)); + + public static ComplexNumber Atanh(ComplexNumber z) => 0.5 * Ln((One + z) / (One - z)); + + public static ComplexNumber Cosh(ComplexNumber z) + => new(Real.Cosh(z._real) * Real.Cos(z._imaginary), Real.Sinh(z._real) * Real.Sin(z._imaginary)); + + public static ComplexNumber Sinh(ComplexNumber z) + => new(Real.Sinh(z._real) * Real.Cos(z._imaginary), Real.Cosh(z._real) * Real.Sin(z._imaginary)); + + public static ComplexNumber Tanh(ComplexNumber z) => Sinh(z) / Cosh(z); + + // Logarithmic functions + + public static ComplexNumber Ln(ComplexNumber z) => new(Real.Ln(Hypot(z._real.Value, z._imaginary.Value)), Real.Atan2(z._imaginary, z._real)); + + public static ComplexNumber Log(ComplexNumber z, ComplexNumber b) => Ln(z) / Ln(b); + + public static ComplexNumber Log2(ComplexNumber z) => Ln(z) / Ln(Real.Ln2); + + public static ComplexNumber Log10(ComplexNumber z) => Ln(z) / Ln(Real.Ln10); + + // Power functions + + public static ComplexNumber Pow(ComplexNumber z, ComplexNumber w) => Exp(w * Ln(z)); + + // Root functions + + public static ComplexNumber Cbrt(ComplexNumber z) => Exp(Ln(z) / 3.0); + + public static ComplexNumber Root(ComplexNumber z, ComplexNumber w) => Exp(Ln(z) / w); + + public static ComplexNumber Sqrt(ComplexNumber z) => Exp(0.5 * Ln(z)); + + // Trigonometric functions + + public static ComplexNumber Acos(ComplexNumber z) + { + AsinInternal(Real.Abs(z._real), Real.Abs(z._imaginary), out Real b, out Real bPrime, out Real v); + + Real u; + if (bPrime < Real.Zero) + { + u = Real.Acos(b); + } + else + { + u = Real.Atan(Real.One / bPrime); + } + + if (z._real < Real.Zero) + { + u = Real.Pi - u; + } + if (z._imaginary > Real.Zero) + { + v = -v; + } + + return new(u, v); + } + + public static ComplexNumber Asin(ComplexNumber z) + { + AsinInternal(Real.Abs(z._real), Real.Abs(z._imaginary), out Real b, out Real bPrime, out Real v); + + Real u; + if (bPrime < Real.Zero) + { + u = Real.Asin(b); + } + else + { + u = Real.Atan(bPrime); + } + + if (z._real < Real.Zero) + { + u = -u; + } + if (z._imaginary < Real.Zero) + { + v = -v; + } + + return new(u, v); + } + + private static void AsinInternal(Real x, Real y, out Real b, out Real bPrime, out Real v) + { + // This is the same method described by Hull, Fairgrieve, and Tang in "Implementing the Complex + // ArcSine and Arccosine Functions Using Exception Handling" that is used in System.Numerics.Complex. + if (x > s_asinOverflowThreshold || y > s_asinOverflowThreshold) + { + b = -Real.One; + bPrime = x / y; + + Real small, big; + if (x < y) + { + small = x; + big = y; + } + else + { + small = y; + big = x; + } + Real ratio = small / big; + v = Real.Ln2 + Real.Ln(big) + Real.Ln(ratio * ratio + Real.One) / 2.0; + } + else + { + Real r = Hypot((x + Real.One).Value, y.Value); + Real s = Hypot((x - Real.One).Value, y.Value); + + Real a = (r + s) / 2.0; + b = x / a; + + if (b > 0.75) + { + if (x <= Real.One) + { + Real amx = (y * y / (r + (x + Real.One)) + (s + (Real.One - x))) / 2.0; + bPrime = x / Real.Sqrt((a + x) * amx); + } + else + { + Real t = (Real.One / (r + (x + Real.One)) + Real.One / (s + (x - Real.One))) / 2.0; + bPrime = x / y / Real.Sqrt((a + x) * t); + } + } + else + { + bPrime = -Real.One; + } + + if (a < 1.5) + { + if (x < Real.One) + { + Real t = (Real.One / (r + (x + Real.One)) + Real.One / (s + (Real.One - x))) / 2.0; + Real am1 = y * y * t; + v = Real.Ln(am1 + y * Real.Sqrt(t * (a + Real.One)) + Real.One); + } + else + { + Real am1 = (y * y / (r + (x + Real.One)) + (s + (x - Real.One))) / 2.0; + v = Real.Ln(am1 + Real.Sqrt(am1 * (a + Real.One)) + Real.One); + } + } + else + { + v = Real.Ln(a + Real.Sqrt((a - Real.One) * (a + Real.One))); + } + } + } + + public static ComplexNumber Atan(ComplexNumber z) => s_imOverTwo * Ln((s_im + z) / (s_im - z)); + + public static ComplexNumber Cos(ComplexNumber z) + { + Real p = Real.Exp(z._imaginary); + Real q = Real.One / p; + Real sinh = (p - q) / 2.0; + Real cosh = (p + q) / 2.0; + return new(Real.Cos(z._real) * cosh, -Real.Sin(z._real) * sinh); + } + + public static ComplexNumber Sin(ComplexNumber z) + { + Real p = Real.Exp(z._imaginary); + Real q = Real.One / p; + Real sinh = (p - q) / 2.0; + Real cosh = (p + q) / 2.0; + return new(Real.Sin(z._real) * cosh, Real.Cos(z._real) * sinh); + } + + public static ComplexNumber Tan(ComplexNumber z) + { + Real x2 = 2.0 * z._real; + Real y2 = 2.0 * z._imaginary; + Real p = Real.Exp(y2); + Real q = Real.One / p; + Real cosh = (p + q) / 2.0; + if (Real.Abs(z._imaginary) <= 4.0) + { + Real sinh = (p - q) / 2.0; + Real D = Real.Cos(x2) + cosh; + return new(Real.Sin(x2) / D, sinh / D); + } + else + { + Real D = Real.One + Real.Cos(x2) / cosh; + return new(Real.Sin(x2) / cosh / D, Real.Tanh(y2) / D); + } + } + + // + // Implicit Operators + // + + public static implicit operator ComplexNumber(double x) => new(x); + + /// Convert a value of type to one of type + /// The value to convert + public static implicit operator ComplexNumber(Real x) => new(x); +} diff --git a/src/Mathematics.NET/Core/Extensions.cs b/src/Mathematics.NET/Core/Extensions.cs index 8ccd7740..fdff5455 100644 --- a/src/Mathematics.NET/Core/Extensions.cs +++ b/src/Mathematics.NET/Core/Extensions.cs @@ -29,10 +29,11 @@ namespace Mathematics.NET.Core; +/// Extension methods for Mathematics.NET public static class Extensions { - public static Rational Reduce(this Rational r) + /// + public static Rational Reduce(this Rational r) where T : IBinaryInteger - where U : IFloatingPointIeee754, IMinMaxValue - => Rational.Reduce(r); + => Rational.Reduce(r); } diff --git a/src/Mathematics.NET/Core/IComplex.cs b/src/Mathematics.NET/Core/IComplex.cs index 0c0f4d18..40607a11 100644 --- a/src/Mathematics.NET/Core/IComplex.cs +++ b/src/Mathematics.NET/Core/IComplex.cs @@ -36,8 +36,7 @@ namespace Mathematics.NET.Core; /// Defines support for complex numbers /// The type that implements the interface -/// A type that implements and -public interface IComplex +public interface IComplex : IAdditionOperation, IDivisionOperation, IMultiplicationOperation, @@ -47,20 +46,19 @@ public interface IComplex IEquatable, ISpanFormattable, ISpanParsable - where T : IComplex - where U : IFloatingPointIeee754, IMinMaxValue + where T : IComplex { /// The real part of the complex number - public Real Re { get; } + public Real Re { get; } /// The imaginary part of the complex number - public virtual Real Im => Real.Zero; + public virtual Real Im => Real.Zero; /// The magnitude of the complex number in polar coordinates - public virtual Real Magnitude => U.Hypot(Re.Value, Im.Value); + public virtual Real Magnitude => Real.Hypot(Re, Im); /// The phase of the complex number in polar coordinates - public virtual Real Phase => U.Atan2(Im.Value, Re.Value); + public virtual Real Phase => Real.Atan2(Im, Re); /// Reprsents zero for the type static abstract T Zero { get; } @@ -68,9 +66,6 @@ public interface IComplex /// Represents one for the type static abstract T One { get; } - /// Represents two for the type - static abstract T Two { get; } - /// Represents NaN for the type static abstract T NaN { get; } @@ -85,17 +80,17 @@ public interface IComplex static abstract T Conjugate(T z); /// Convert a value to one of the current type, and throw and overflow exception if the value falls outside the representable range. - /// The type from which to convert + /// The type from which to convert /// The value to convert /// The result of the conversion - /// Conversions from the type are not supported. + /// Conversions from the type are not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] - static virtual T CreateChecked(V value) - where V : INumberBase + static virtual T CreateChecked(U value) + where U : INumberBase { T? result; - if (typeof(V) == typeof(T)) + if (typeof(U) == typeof(T)) { result = (T)(object)value; } @@ -108,17 +103,17 @@ static virtual T CreateChecked(V value) } /// Convert a value to one of the current type, and saturate values that fall outside the representable range. - /// The type from which to convert + /// The type from which to convert /// The value to convert /// The result of the conversion - /// Conversions from the type are not supported. + /// Conversions from the type are not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] - static virtual T CreateSaturating(V value) - where V : INumberBase + static virtual T CreateSaturating(U value) + where U : INumberBase { T? result; - if (typeof(V) == typeof(T)) + if (typeof(U) == typeof(T)) { result = (T)(object)value; } @@ -131,17 +126,17 @@ static virtual T CreateSaturating(V value) } /// Convert a value to one of another type, and truncate values that fall outside of the representable range. - /// The type from which to convert + /// The type from which to convert /// The value to convert /// The result of the conversion - /// Conversions from the type are not supported. + /// Conversions from the type are not supported. [MethodImpl(MethodImplOptions.AggressiveInlining)] - static virtual T CreateTruncating(V value) - where V : INumberBase + static virtual T CreateTruncating(U value) + where U : INumberBase { T? result; - if (typeof(V) == typeof(T)) + if (typeof(U) == typeof(T)) { result = (T)(object)value; } @@ -197,54 +192,54 @@ static virtual T CreateTruncating(V value) static abstract T Reciprocate(T z); /// Try to convert a value to one of the current type, and throw and overflow exception if the value falls outside the representable range. - /// The type from which to convert + /// The type from which to convert /// The value to convert /// The result of the conversion /// true if the conversion was successful; otherwise, false /// The value is not representable by the type . - protected static abstract bool TryConvertFromChecked(V value, [MaybeNullWhen(false)] out T result) - where V : INumberBase; + protected static abstract bool TryConvertFromChecked(U value, [MaybeNullWhen(false)] out T result) + where U : INumberBase; /// Try to convert a value to one of the current type, and saturate values that fall outside the representable range. - /// The type from which to convert + /// The type from which to convert /// The value to convert /// The result of the conversion /// true if the conversion was successful; otherwise, false - protected static abstract bool TryConvertFromSaturating(V value, [MaybeNullWhen(false)] out T result) - where V : INumberBase; + protected static abstract bool TryConvertFromSaturating(U value, [MaybeNullWhen(false)] out T result) + where U : INumberBase; /// Try to convert a value to one of the current type, and truncate values that fall outside the representable range. - /// The type from which to convert + /// The type from which to convert /// The value to convert /// The result of the conversion /// true if the conversion was successful; otherwise, false - protected static abstract bool TryConvertFromTruncating(V value, [MaybeNullWhen(false)] out T result) - where V : INumberBase; + protected static abstract bool TryConvertFromTruncating(U value, [MaybeNullWhen(false)] out T result) + where U : INumberBase; /// Try to convert a value to one of another type, and throw and overflow exception if the value falls outside the representable range. - /// The target type + /// The target type /// The value to convert /// The result of the conversion /// true if the conversion was successful; otherwise, false /// The value is not representable by the target type. - protected static abstract bool TryConvertToChecked(T value, [MaybeNullWhen(false)] out V result) - where V : INumberBase; + protected static abstract bool TryConvertToChecked(T value, [MaybeNullWhen(false)] out U result) + where U : INumberBase; /// Try to convert a value to one of another type, and saturate values that fall outside of the representable range. - /// The target type + /// The target type /// The value to convert /// The result of the conversion /// true if the conversion was successful; otherwise, false - protected static abstract bool TryConvertToSaturating(T value, [MaybeNullWhen(false)] out V result) - where V : INumberBase; + protected static abstract bool TryConvertToSaturating(T value, [MaybeNullWhen(false)] out U result) + where U : INumberBase; /// Try to convert a value to one of another type, and truncate values that fall outside of the representable range. - /// The target type + /// The target type /// The value to convert /// The result of the conversion /// true if the conversion was successful; otherwise, false - protected static abstract bool TryConvertToTruncating(T value, [MaybeNullWhen(false)] out V result) - where V : INumberBase; + protected static abstract bool TryConvertToTruncating(T value, [MaybeNullWhen(false)] out U result) + where U : INumberBase; /// Try to parse a string into a value /// The string to parse @@ -266,5 +261,5 @@ protected static abstract bool TryConvertToTruncating(T value, [MaybeNullWhen /// Convert a value of type to one of type /// The value to convert - static virtual implicit operator T(U x) => T.CreateTruncating(x); + static virtual implicit operator T(double x) => T.CreateSaturating(x); } diff --git a/src/Mathematics.NET/Core/IConstants.cs b/src/Mathematics.NET/Core/IConstants.cs index b9cbd048..b7af8e79 100644 --- a/src/Mathematics.NET/Core/IConstants.cs +++ b/src/Mathematics.NET/Core/IConstants.cs @@ -25,16 +25,12 @@ // SOFTWARE. // -using System.Numerics; - namespace Mathematics.NET.Core; /// Defines support for common mathematical constants /// The type that implements the interface -/// A type that implements and -public interface IConstants : IComplex - where T : IConstants - where U : IFloatingPointIeee754, IMinMaxValue +public interface IConstants : IComplex + where T : IConstants { /// static abstract T E { get; } diff --git a/src/Mathematics.NET/Core/IDifferentiableFunctions.cs b/src/Mathematics.NET/Core/IDifferentiableFunctions.cs index 60011e7e..a57d4e92 100644 --- a/src/Mathematics.NET/Core/IDifferentiableFunctions.cs +++ b/src/Mathematics.NET/Core/IDifferentiableFunctions.cs @@ -25,16 +25,12 @@ // SOFTWARE. // -using System.Numerics; - namespace Mathematics.NET.Core; /// Defines support for common differentiable functions /// The type that implements the interface -/// A type that implements and -public interface IDifferentiableFunctions : IComplex - where T : IDifferentiableFunctions - where U : IFloatingPointIeee754, IMinMaxValue +public interface IDifferentiableFunctions : IComplex + where T : IDifferentiableFunctions { // // Exponential functions diff --git a/src/Mathematics.NET/Core/IRational.cs b/src/Mathematics.NET/Core/IRational.cs index afabffbf..5341fed2 100644 --- a/src/Mathematics.NET/Core/IRational.cs +++ b/src/Mathematics.NET/Core/IRational.cs @@ -32,11 +32,9 @@ namespace Mathematics.NET.Core; /// Defines support for rational numbers /// A type that implements the interface /// A type that implements -/// A type that implements and -public interface IRational : IReal - where T : IRational +public interface IRational : IReal + where T : IRational where U : IBinaryInteger - where V : IFloatingPointIeee754, IMinMaxValue { /// Get the numerator of the rational number U Num { get; } diff --git a/src/Mathematics.NET/Core/IReal.cs b/src/Mathematics.NET/Core/IReal.cs index 21698570..8169c6bb 100644 --- a/src/Mathematics.NET/Core/IReal.cs +++ b/src/Mathematics.NET/Core/IReal.cs @@ -33,20 +33,18 @@ namespace Mathematics.NET.Core; /// Defines support for real numbers /// A type that implements the interface -/// A type that implements and -public interface IReal - : IComplex, +public interface IReal + : IComplex, IInequalityRelations, IDecrementOperation, IIncrementOperation, IComparable, IComparable, IMinMaxValue - where T : IReal - where U : IFloatingPointIeee754, IMinMaxValue + where T : IReal { /// The backing value of the type - U Value { get; } + double Value { get; } /// Check if a value is negative infinity /// The value to check diff --git a/src/Mathematics.NET/Core/Rational.cs b/src/Mathematics.NET/Core/Rational.cs index 84896777..ea97283d 100644 --- a/src/Mathematics.NET/Core/Rational.cs +++ b/src/Mathematics.NET/Core/Rational.cs @@ -35,25 +35,20 @@ namespace Mathematics.NET.Core; /// Represents a rational number /// A type that implements -/// A type that implements and [Serializable] [StructLayout(LayoutKind.Sequential)] -public readonly struct Rational : IRational, T, U> +public readonly struct Rational : IRational, T> where T : IBinaryInteger - where U : IFloatingPointIeee754, IMinMaxValue { - private static U s_ten = U.Exp10(U.One); + public static readonly Rational Zero = T.Zero; + public static readonly Rational One = T.One; - public static readonly Rational Zero = T.Zero; - public static readonly Rational One = T.One; - public static readonly Rational Two = T.One + T.One; + public static readonly Rational MaxValue = T.CreateSaturating(double.MaxValue); + public static readonly Rational MinValue = T.CreateSaturating(double.MinValue); - public static readonly Rational MaxValue = T.CreateSaturating(U.MaxValue); - public static readonly Rational MinValue = T.CreateSaturating(U.MinValue); - - public static readonly Rational NaN = new(T.Zero, T.Zero); - public static readonly Rational NegativeInfinity = new(-T.One, T.Zero); - public static readonly Rational PositiveInfinity = new(T.One, T.Zero); + public static readonly Rational NaN = new(T.Zero, T.Zero); + public static readonly Rational NegativeInfinity = new(-T.One, T.Zero); + public static readonly Rational PositiveInfinity = new(T.One, T.Zero); private readonly T _numerator; private readonly T _denominator; @@ -96,31 +91,30 @@ public Rational(T p, T q) public T Num => _numerator; public T Den => _denominator; - public Real Re => (U)this; - public U Value => (U)this; + public Real Re => (Real)this; + public double Value => (double)this; // // Constants // - static Rational IComplex, U>.Zero => Zero; - static Rational IComplex, U>.One => One; - static Rational IComplex, U>.Two => Zero; - static Rational IComplex, U>.NaN => NaN; - static Rational IMinMaxValue>.MaxValue => MaxValue; - static Rational IMinMaxValue>.MinValue => MinValue; + static Rational IComplex>.Zero => Zero; + static Rational IComplex>.One => One; + static Rational IComplex>.NaN => NaN; + static Rational IMinMaxValue>.MaxValue => MaxValue; + static Rational IMinMaxValue>.MinValue => MinValue; // // Operators // - public static Rational operator -(Rational x) => x + One; + public static Rational operator -(Rational x) => x + One; - public static Rational operator --(Rational x) => new(x._numerator, x._denominator); + public static Rational operator --(Rational x) => new(x._numerator, x._denominator); - public static Rational operator ++(Rational x) => new(x._numerator, x._denominator); + public static Rational operator ++(Rational x) => new(x._numerator, x._denominator); - public static Rational operator +(Rational x, Rational y) + public static Rational operator +(Rational x, Rational y) { var lcm = LCM(x._denominator, y._denominator); var num = lcm / x._denominator * x._numerator + lcm / y._denominator * y._numerator; @@ -128,7 +122,7 @@ public Rational(T p, T q) return new(num / gcd, lcm / gcd); } - public static Rational operator -(Rational x, Rational y) + public static Rational operator -(Rational x, Rational y) { var lcm = LCM(x._denominator, y._denominator); var num = lcm / x._denominator * x._numerator - lcm / y._denominator * y._numerator; @@ -136,7 +130,7 @@ public Rational(T p, T q) return new(num / gcd, lcm / gcd); } - public static Rational operator *(Rational x, Rational y) + public static Rational operator *(Rational x, Rational y) { var num = x._numerator * y._numerator; var den = x._denominator * y._denominator; @@ -144,7 +138,7 @@ public Rational(T p, T q) return new(num / gcd, den / gcd); } - public static Rational operator /(Rational x, Rational y) + public static Rational operator /(Rational x, Rational y) { if (y._denominator == T.Zero) { @@ -161,19 +155,19 @@ public Rational(T p, T q) // Equality // - public static bool operator ==(Rational left, Rational right) + public static bool operator ==(Rational left, Rational right) { return left._numerator == right._numerator && left._denominator == right._denominator; } - public static bool operator !=(Rational left, Rational right) + public static bool operator !=(Rational left, Rational right) { return left._numerator != right._numerator || left._denominator != right._denominator; } - public override bool Equals([NotNullWhen(true)] object? obj) => obj is Rational other && Equals(other); + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Rational other && Equals(other); - public bool Equals(Rational value) + public bool Equals(Rational value) { return _numerator.Equals(value._numerator) && _denominator.Equals(value._denominator); } @@ -184,22 +178,22 @@ public bool Equals(Rational value) // Comparison // - public static bool operator <(Rational x, Rational y) + public static bool operator <(Rational x, Rational y) { return x._numerator * y._denominator < y._numerator * x._denominator; } - public static bool operator >(Rational x, Rational y) + public static bool operator >(Rational x, Rational y) { return x._numerator * y._denominator > y._numerator * x._denominator; } - public static bool operator <=(Rational x, Rational y) + public static bool operator <=(Rational x, Rational y) { return x._numerator * y._denominator <= y._numerator * x._denominator; } - public static bool operator >=(Rational x, Rational y) + public static bool operator >=(Rational x, Rational y) { return x._numerator * y._denominator >= y._numerator * x._denominator; } @@ -211,7 +205,7 @@ public int CompareTo(object? obj) return 1; } - if (obj is Rational other) + if (obj is Rational other) { return CompareTo(other); } @@ -219,7 +213,7 @@ public int CompareTo(object? obj) throw new ArgumentException("Argument is not a rational number"); } - public int CompareTo(Rational value) + public int CompareTo(Rational value) { if (this < value) { @@ -378,35 +372,35 @@ static bool TryFormatAllInternal(T num, T den, Span destination, out int c // Parsing // - public static Rational Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); + public static Rational Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); - public static Rational Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); + public static Rational Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); - public static Rational Parse(string s, NumberStyles style, IFormatProvider? provider) + public static Rational Parse(string s, NumberStyles style, IFormatProvider? provider) { ArgumentNullException.ThrowIfNull(s); return Parse((ReadOnlySpan)s, style, provider); } - public static Rational Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + public static Rational Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) { - if (!TryParse(s, style, provider, out Rational result)) + if (!TryParse(s, style, provider, out Rational result)) { return NaN; } return result; } - public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Rational result) + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Rational result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); - public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Rational result) + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Rational result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); - public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Rational result) + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Rational result) => TryParse((ReadOnlySpan)s, style, provider, out result); - public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Rational result) + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Rational result) { s = s.Trim(); int openParenthesis = s.IndexOf('('); @@ -440,9 +434,9 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro // Methods // - public static Rational Abs(Rational x) => new(T.Abs(x._numerator), T.Abs(x._denominator)); + public static Rational Abs(Rational x) => new(T.Abs(x._numerator), T.Abs(x._denominator)); - public static Rational Conjugate(Rational x) => x; + public static Rational Conjugate(Rational x) => x; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T GCD(T p, T q) @@ -463,17 +457,17 @@ private static T GCD(T p, T q) return p | q; } - public static bool IsFinite(Rational x) => !T.IsZero(x._denominator); + public static bool IsFinite(Rational x) => !T.IsZero(x._denominator); - public static bool IsInfinity(Rational x) => T.IsZero(x._denominator); + public static bool IsInfinity(Rational x) => T.IsZero(x._denominator); - public static bool IsNaN(Rational x) => T.IsZero(x._numerator) && T.IsZero(x._denominator); + public static bool IsNaN(Rational x) => T.IsZero(x._numerator) && T.IsZero(x._denominator); - public static bool IsZero(Rational x) => T.IsZero(x._numerator); + public static bool IsZero(Rational x) => T.IsZero(x._numerator); - public static bool IsNegativeInfinity(Rational x) => x._numerator == -T.One && T.IsZero(x._denominator); + public static bool IsNegativeInfinity(Rational x) => x._numerator == -T.One && T.IsZero(x._denominator); - public static bool IsPositiveInfinity(Rational x) => x._numerator == T.One && T.IsZero(x._denominator); + public static bool IsPositiveInfinity(Rational x) => x._numerator == T.One && T.IsZero(x._denominator); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T LCM(T p, T q) @@ -496,7 +490,7 @@ private static T LCM(T p, T q) return holdP / (p | q) * holdQ; } - public static Rational Reciprocate(Rational x) + public static Rational Reciprocate(Rational x) { if (x._numerator == T.Zero) { @@ -505,7 +499,7 @@ public static Rational Reciprocate(Rational x) return new(x._denominator, x._numerator); } - public static Rational Reduce(Rational x) + public static Rational Reduce(Rational x) { var gcd = GCD(x._numerator, x._denominator); if (gcd == T.One) @@ -515,17 +509,12 @@ public static Rational Reduce(Rational x) return new(x._numerator / gcd, x._denominator / gcd); } - public static bool TryConvertFromChecked(V value, out Rational result) - where V : INumberBase + public static bool TryConvertFromChecked(U value, out Rational result) + where U : INumberBase { - if (V.IsInteger(value)) - { - result = T.CreateChecked(value); - return true; - } - else if (value is IFloatingPointIeee754 floatingPointNumber) + if (Real.TryConvertFromChecked(value, out var intermediateResult)) { - result = (Rational)(U)floatingPointNumber; + result = (Rational)intermediateResult; return true; } else @@ -535,17 +524,12 @@ public static bool TryConvertFromChecked(V value, out Rational result) } } - public static bool TryConvertFromSaturating(V value, out Rational result) - where V : INumberBase + public static bool TryConvertFromSaturating(U value, out Rational result) + where U : INumberBase { - if (V.IsInteger(value)) - { - result = T.CreateSaturating(value); - return true; - } - else if (value is IFloatingPointIeee754 floatingPointNumber) + if (Real.TryConvertFromSaturating(value, out var intermediateResult)) { - result = (Rational)(U)floatingPointNumber; + result = (Rational)intermediateResult; return true; } else @@ -555,17 +539,12 @@ public static bool TryConvertFromSaturating(V value, out Rational resul } } - public static bool TryConvertFromTruncating(V value, out Rational result) - where V : INumberBase + public static bool TryConvertFromTruncating(U value, out Rational result) + where U : INumberBase { - if (V.IsInteger(value)) - { - result = T.CreateTruncating(value); - return true; - } - else if (value is IFloatingPointIeee754 floatingPointNumber) + if (Real.TryConvertFromTruncating(value, out var intermediateResult)) { - result = (Rational)(U)floatingPointNumber; + result = (Rational)intermediateResult; return true; } else @@ -575,24 +554,24 @@ public static bool TryConvertFromTruncating(V value, out Rational resul } } - public static bool TryConvertToChecked(Rational value, [MaybeNullWhen(false)] out V result) - where V : INumberBase + public static bool TryConvertToChecked(Rational value, [MaybeNullWhen(false)] out U result) + where U : INumberBase { - result = V.CreateChecked(checked((U)value)); + result = U.CreateChecked(checked(double.CreateChecked(value._numerator) / double.CreateChecked(value._denominator))); return true; } - public static bool TryConvertToSaturating(Rational value, [MaybeNullWhen(false)] out V result) - where V : INumberBase + public static bool TryConvertToSaturating(Rational value, [MaybeNullWhen(false)] out U result) + where U : INumberBase { - result = V.CreateSaturating(U.CreateSaturating(value._numerator) / U.CreateSaturating(value._denominator)); + result = U.CreateSaturating(double.CreateSaturating(value._numerator) / double.CreateSaturating(value._denominator)); return true; } - public static bool TryConvertToTruncating(Rational value, [MaybeNullWhen(false)] out V result) - where V : INumberBase + public static bool TryConvertToTruncating(Rational value, [MaybeNullWhen(false)] out U result) + where U : INumberBase { - result = V.CreateTruncating(U.CreateTruncating(value._numerator) / U.CreateTruncating(value._denominator)); + result = U.CreateTruncating(double.CreateTruncating(value._numerator) / double.CreateTruncating(value._denominator)); return true; } @@ -600,35 +579,40 @@ public static bool TryConvertToTruncating(Rational value, [MaybeNullWhe // Implicit operators // - public static implicit operator Rational(T p) => new(p); + public static implicit operator Rational(T p) => new(p); // // Explicit operators // // TODO: Find a better implementation - public static explicit operator Rational(U x) + public static explicit operator Rational(Real x) { - if (U.IsNaN(x) || U.IsInfinity(x)) + var value = x.Value; + if (double.IsNaN(value) || double.IsInfinity(value)) { return NaN; } - var n = U.Zero; - while (x != U.Floor(x)) + var n = 0.0; + while (x != double.Floor(value)) { - x *= s_ten; + x *= 10.0; n++; } - T num = T.CreateChecked(x); - T den = T.CreateChecked(U.Pow(s_ten, n)); + T num = T.CreateChecked(value); + T den = T.CreateChecked(Math.Pow(10.0, n)); var gcd = GCD(num, den); return new(num / gcd, den / gcd); } - public static explicit operator checked U(Rational x) => checked(U.CreateChecked(x._numerator) / U.CreateChecked(x._denominator)); + public static explicit operator checked Real(Rational x) => checked(double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator)); + + public static explicit operator Real(Rational x) => double.CreateSaturating(x._numerator) / double.CreateSaturating(x._denominator); + + public static explicit operator checked double(Rational x) => double.CreateChecked(x._numerator) / double.CreateChecked(x._denominator); - public static explicit operator U(Rational x) => U.CreateChecked(x._numerator) / U.CreateChecked(x._denominator); + public static explicit operator double(Rational x) => double.CreateSaturating(x._numerator) / double.CreateSaturating(x._denominator); } diff --git a/src/Mathematics.NET/Core/Real.cs b/src/Mathematics.NET/Core/Real.cs index 48fef501..b935a07c 100644 --- a/src/Mathematics.NET/Core/Real.cs +++ b/src/Mathematics.NET/Core/Real.cs @@ -36,71 +36,69 @@ namespace Mathematics.NET.Core; /// A type that implements and [Serializable] [StructLayout(LayoutKind.Sequential)] -public readonly struct Real - : IReal, T>, - IConstants, T>, - IDifferentiableFunctions, T> - where T : IFloatingPointIeee754, IMinMaxValue +public readonly struct Real + : IReal, + IConstants, + IDifferentiableFunctions { - public static readonly Real Zero = T.Zero; - public static readonly Real One = T.One; - public static readonly Real Two = T.One + T.One; + public static readonly Real Zero = 0.0; + public static readonly Real One = 1.0; - public static readonly Real MaxValue = T.MaxValue; - public static readonly Real MinValue = T.MinValue; + public static readonly Real MaxValue = double.MaxValue; + public static readonly Real MinValue = double.MinValue; - public static readonly Real NaN = T.NaN; - public static readonly Real NegativeInfinity = T.NegativeInfinity; - public static readonly Real PositiveInfinity = T.PositiveInfinity; + public static readonly Real NaN = double.NaN; + public static readonly Real NegativeInfinity = double.NegativeInfinity; + public static readonly Real PositiveInfinity = double.PositiveInfinity; /// - public static readonly Real E = T.CreateTruncating(Constants.E); + public static readonly Real E = Constants.E; /// - public static readonly Real Pi = T.CreateTruncating(Constants.Pi); + public static readonly Real Pi = Constants.Pi; /// - public static readonly Real PiOverTwo = T.CreateTruncating(Constants.PiOverTwo); + public static readonly Real PiOverTwo = Constants.PiOverTwo; /// - public static readonly Real PiSquared = T.CreateTruncating(Constants.PiSquared); + public static readonly Real PiSquared = Constants.PiSquared; /// - public static readonly Real Tau = T.CreateTruncating(Constants.Tau); + public static readonly Real Tau = Constants.Tau; /// - public static readonly Real EulerMascheroni = T.CreateTruncating(Constants.EulerMascheroni); + public static readonly Real EulerMascheroni = Constants.EulerMascheroni; /// - public static readonly Real GoldenRatio = T.CreateTruncating(Constants.GoldenRatio); + public static readonly Real GoldenRatio = Constants.GoldenRatio; /// - public static readonly Real Ln2 = T.CreateTruncating(Constants.Ln2); + public static readonly Real Ln2 = Constants.Ln2; /// - public static readonly Real Ln10 = T.CreateTruncating(Constants.Ln10); + public static readonly Real Ln10 = Constants.Ln10; /// - public static readonly Real Sqrt2 = T.CreateTruncating(Constants.Sqrt2); + public static readonly Real Sqrt2 = Constants.Sqrt2; /// - public static readonly Real Sqrt3 = T.CreateTruncating(Constants.Sqrt3); + public static readonly Real Sqrt3 = Constants.Sqrt3; /// - public static readonly Real Sqrt5 = T.CreateTruncating(Constants.Sqrt5); + public static readonly Real Sqrt5 = Constants.Sqrt5; /// - public static readonly Real ZetaOf2 = T.CreateTruncating(Constants.ZetaOf2); + public static readonly Real ZetaOf2 = Constants.ZetaOf2; /// - public static readonly Real ZetaOf3 = T.CreateTruncating(Constants.ZetaOf3); + public static readonly Real ZetaOf3 = Constants.ZetaOf3; /// - public static readonly Real ZetaOf4 = T.CreateTruncating(Constants.ZetaOf4); + public static readonly Real ZetaOf4 = Constants.ZetaOf4; - private readonly T _value; + private readonly double _value; - public Real(T real) + public Real(double real) { _value = real; } @@ -109,61 +107,60 @@ public Real(T real) // Real number properties // - public Real Re => _value; - public T Value => _value; + public Real Re => _value; + public double Value => _value; // // Constants // - static Real IComplex, T>.Zero => Zero; - static Real IComplex, T>.One => One; - static Real IComplex, T>.Two => Two; - static Real IComplex, T>.NaN => NaN; - static Real IMinMaxValue>.MaxValue => MaxValue; - static Real IMinMaxValue>.MinValue => MinValue; + static Real IComplex.Zero => Zero; + static Real IComplex.One => One; + static Real IComplex.NaN => NaN; + static Real IMinMaxValue.MaxValue => MaxValue; + static Real IMinMaxValue.MinValue => MinValue; // IConstants interface - static Real IConstants, T>.E => E; - static Real IConstants, T>.Pi => Pi; - static Real IConstants, T>.PiOverTwo => PiOverTwo; - static Real IConstants, T>.PiSquared => PiSquared; - static Real IConstants, T>.Tau => Tau; - static Real IConstants, T>.EulerMascheroni => EulerMascheroni; - static Real IConstants, T>.GoldenRatio => GoldenRatio; - static Real IConstants, T>.Ln2 => Ln2; - static Real IConstants, T>.Ln10 => Ln10; - static Real IConstants, T>.Sqrt2 => Sqrt2; - static Real IConstants, T>.Sqrt3 => Sqrt3; - static Real IConstants, T>.Sqrt5 => Sqrt5; - static Real IConstants, T>.ZetaOf2 => ZetaOf2; - static Real IConstants, T>.ZetaOf3 => ZetaOf3; - static Real IConstants, T>.ZetaOf4 => ZetaOf4; + static Real IConstants.E => E; + static Real IConstants.Pi => Pi; + static Real IConstants.PiOverTwo => PiOverTwo; + static Real IConstants.PiSquared => PiSquared; + static Real IConstants.Tau => Tau; + static Real IConstants.EulerMascheroni => EulerMascheroni; + static Real IConstants.GoldenRatio => GoldenRatio; + static Real IConstants.Ln2 => Ln2; + static Real IConstants.Ln10 => Ln10; + static Real IConstants.Sqrt2 => Sqrt2; + static Real IConstants.Sqrt3 => Sqrt3; + static Real IConstants.Sqrt5 => Sqrt5; + static Real IConstants.ZetaOf2 => ZetaOf2; + static Real IConstants.ZetaOf3 => ZetaOf3; + static Real IConstants.ZetaOf4 => ZetaOf4; // // Operators // - public static Real operator -(Real value) => -value._value; - public static Real operator --(Real value) => value._value - One; - public static Real operator ++(Real value) => value._value + One; - public static Real operator +(Real left, Real right) => left._value + right._value; - public static Real operator -(Real left, Real right) => left._value - right._value; - public static Real operator *(Real left, Real right) => left._value * right._value; - public static Real operator /(Real left, Real right) => left._value / right._value; + public static Real operator -(Real value) => -value._value; + public static Real operator --(Real value) => value._value - One; + public static Real operator ++(Real value) => value._value + One; + public static Real operator +(Real left, Real right) => left._value + right._value; + public static Real operator -(Real left, Real right) => left._value - right._value; + public static Real operator *(Real left, Real right) => left._value * right._value; + public static Real operator /(Real left, Real right) => left._value / right._value; // // Equality // - public static bool operator ==(Real left, Real right) => left._value == right._value; + public static bool operator ==(Real left, Real right) => left._value == right._value; - public static bool operator !=(Real left, Real right) => left._value != right._value; + public static bool operator !=(Real left, Real right) => left._value != right._value; - public override bool Equals([NotNullWhen(true)] object? obj) => obj is Real other && Equals(other); + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Real other && Equals(other); - public bool Equals(Real value) => _value.Equals(value._value); + public bool Equals(Real value) => _value.Equals(value._value); public override int GetHashCode() => HashCode.Combine(_value); @@ -171,10 +168,10 @@ public Real(T real) // Comparison // - public static bool operator <(Real left, Real right) => left._value < right._value; - public static bool operator >(Real left, Real right) => left._value > right._value; - public static bool operator <=(Real left, Real right) => left._value <= right._value; - public static bool operator >=(Real left, Real right) => left._value >= right._value; + public static bool operator <(Real left, Real right) => left._value < right._value; + public static bool operator >(Real left, Real right) => left._value > right._value; + public static bool operator <=(Real left, Real right) => left._value <= right._value; + public static bool operator >=(Real left, Real right) => left._value >= right._value; public int CompareTo(object? obj) { @@ -183,7 +180,7 @@ public int CompareTo(object? obj) return 1; } - if (obj is Real value) + if (obj is Real value) { return _value.CompareTo(value._value); } @@ -191,7 +188,7 @@ public int CompareTo(object? obj) throw new ArgumentException("Argument is not a real number"); } - public int CompareTo(Real value) => _value.CompareTo(value._value); + public int CompareTo(Real value) => _value.CompareTo(value._value); // // Formatting @@ -199,7 +196,7 @@ public int CompareTo(object? obj) public override string ToString() => ToString(null, null); - public string ToString(string? format, IFormatProvider? provider) => string.Format(provider, "{0}", _value.ToString(format, provider)); + public string ToString(string? format, IFormatProvider? provider) => string.Format(provider, "{0}", _value); public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => _value.TryFormat(destination, out charsWritten, null, provider); @@ -208,53 +205,53 @@ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan // Parsing // - public static Real Parse(string s, IFormatProvider? provider) => T.Parse(s, provider); + public static Real Parse(string s, IFormatProvider? provider) => double.Parse(s, provider); - public static Real Parse(ReadOnlySpan s, IFormatProvider? provider) => T.Parse(s, provider); + public static Real Parse(ReadOnlySpan s, IFormatProvider? provider) => double.Parse(s, provider); - public static Real Parse(string s, NumberStyles style, IFormatProvider? provider) - => T.Parse(s, style, provider); + public static Real Parse(string s, NumberStyles style, IFormatProvider? provider) + => double.Parse(s, style, provider); - public static Real Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) - => T.Parse(s, style, provider); + public static Real Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + => double.Parse(s, style, provider); - public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Real result) + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Real result) { - var succeeded = T.TryParse(s, provider, out T? number); - result = number!; + var succeeded = double.TryParse(s, provider, out double number); + result = number; return succeeded; } - public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Real result) + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Real result) { - var succeeded = T.TryParse(s, provider, out T? number); - result = number!; + var succeeded = double.TryParse(s, provider, out double number); + result = number; return succeeded; } - public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Real result) + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Real result) { if (s is null) { - result = T.Zero; + result = 0.0; return false; } - var succeeded = T.TryParse((ReadOnlySpan)s, style, NumberFormatInfo.GetInstance(provider), out T? number); - result = number!; + var succeeded = double.TryParse((ReadOnlySpan)s, style, NumberFormatInfo.GetInstance(provider), out double number); + result = number; return succeeded; } - public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Real result) + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Real result) { if (s.IsEmpty) { - result = T.Zero; + result = 0.0; return false; } - var succeeded = T.TryParse(s, style, NumberFormatInfo.GetInstance(provider), out T? number); - result = number!; + var succeeded = double.TryParse(s, style, NumberFormatInfo.GetInstance(provider), out double number); + result = number; return succeeded; } @@ -262,72 +259,72 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro // Methods // - public static Real Abs(Real x) => T.Abs(x._value); + public static Real Abs(Real x) => Math.Abs(x._value); - public static Real Conjugate(Real x) => x; + public static Real Conjugate(Real x) => x; - public static Real Hypot(Real x, Real y) => T.Hypot(x._value, y._value); + public static Real Hypot(Real x, Real y) => double.Hypot(x._value, y._value); - public static bool IsFinite(Real x) => T.IsFinite(x._value); + public static bool IsFinite(Real x) => double.IsFinite(x._value); - public static bool IsInfinity(Real x) => T.IsInfinity(x._value); + public static bool IsInfinity(Real x) => double.IsInfinity(x._value); - public static bool IsNaN(Real x) => T.IsNaN(x._value); + public static bool IsNaN(Real x) => double.IsNaN(x._value); - public static bool IsZero(Real x) => T.IsZero(x._value); + public static bool IsZero(Real x) => x._value == 0.0; - public static bool IsNegativeInfinity(Real x) => T.IsNegativeInfinity(x._value); + public static bool IsNegativeInfinity(Real x) => double.IsNegativeInfinity(x._value); - public static bool IsPositiveInfinity(Real x) => T.IsPositiveInfinity(x._value); + public static bool IsPositiveInfinity(Real x) => double.IsPositiveInfinity(x._value); - public static Real Reciprocate(Real x) + public static Real Reciprocate(Real x) { - if (x._value == T.Zero) + if (x._value == 0.0) { return PositiveInfinity; } - return T.One / x; + return 1.0 / x; } - public static bool TryConvertFromChecked(V value, out Real result) - where V : INumberBase + public static bool TryConvertFromChecked(U value, out Real result) + where U : INumberBase { - result = T.CreateChecked(value); + result = double.CreateChecked(value); return true; } - public static bool TryConvertFromSaturating(V value, out Real result) - where V : INumberBase + public static bool TryConvertFromSaturating(U value, out Real result) + where U : INumberBase { - result = T.CreateSaturating(value); + result = double.CreateSaturating(value); return true; } - public static bool TryConvertFromTruncating(V value, out Real result) - where V : INumberBase + public static bool TryConvertFromTruncating(U value, out Real result) + where U : INumberBase { - result = T.CreateTruncating(value); + result = double.CreateTruncating(value); return true; } - public static bool TryConvertToChecked(Real value, [MaybeNullWhen(false)] out V result) - where V : INumberBase + public static bool TryConvertToChecked(Real value, [MaybeNullWhen(false)] out U result) + where U : INumberBase { - result = V.CreateChecked(value._value); + result = U.CreateChecked(value._value); return true; } - public static bool TryConvertToSaturating(Real value, [MaybeNullWhen(false)] out V result) - where V : INumberBase + public static bool TryConvertToSaturating(Real value, [MaybeNullWhen(false)] out U result) + where U : INumberBase { - result = V.CreateSaturating(value._value); + result = U.CreateSaturating(value._value); return true; } - public static bool TryConvertToTruncating(Real value, [MaybeNullWhen(false)] out V result) - where V : INumberBase + public static bool TryConvertToTruncating(Real value, [MaybeNullWhen(false)] out U result) + where U : INumberBase { - result = V.CreateTruncating(value._value); + result = U.CreateTruncating(value._value); return true; } @@ -337,67 +334,67 @@ public static bool TryConvertToTruncating(Real value, [MaybeNullWhen(false // Exponential functions - public static Real Exp(Real x) => T.Exp(x._value); + public static Real Exp(Real x) => Math.Exp(x._value); - public static Real Exp2(Real x) => T.Exp2(x._value); + public static Real Exp2(Real x) => double.Exp2(x._value); - public static Real Exp10(Real x) => T.Exp10(x._value); + public static Real Exp10(Real x) => double.Exp10(x._value); // Hyperbolic functions - public static Real Acosh(Real x) => T.Acosh(x._value); + public static Real Acosh(Real x) => Math.Acosh(x._value); - public static Real Asinh(Real x) => T.Asinh(x._value); + public static Real Asinh(Real x) => Math.Asinh(x._value); - public static Real Atanh(Real x) => T.Atanh(x._value); + public static Real Atanh(Real x) => Math.Atanh(x._value); - public static Real Cosh(Real x) => T.Cosh(x._value); + public static Real Cosh(Real x) => Math.Cosh(x._value); - public static Real Sinh(Real x) => T.Sinh(x._value); + public static Real Sinh(Real x) => Math.Sinh(x._value); - public static Real Tanh(Real x) => T.Tanh(x._value); + public static Real Tanh(Real x) => Math.Tanh(x._value); // Logarithmic functions - public static Real Ln(Real x) => T.Log(x._value); + public static Real Ln(Real x) => Math.Log(x._value); - public static Real Log(Real x, Real b) => T.Log(x._value, b._value); + public static Real Log(Real x, Real b) => Math.Log(x._value, b._value); - public static Real Log2(Real x) => T.Log2(x._value); + public static Real Log2(Real x) => Math.Log2(x._value); - public static Real Log10(Real x) => T.Log10(x._value); + public static Real Log10(Real x) => Math.Log10(x._value); // Power functions - public static Real Pow(Real x, Real y) => T.Pow(x._value, y._value); + public static Real Pow(Real x, Real y) => Math.Pow(x._value, y._value); // Root functions - public static Real Cbrt(Real x) => T.Cbrt(x._value); + public static Real Cbrt(Real x) => Math.Cbrt(x._value); - public static Real Root(Real x, Real y) => T.Exp(y._value * T.Log(x._value)); + public static Real Root(Real x, Real y) => Math.Exp(Math.Log(x._value) / y._value); - public static Real Sqrt(Real x) => T.Sqrt(x._value); + public static Real Sqrt(Real x) => Math.Sqrt(x._value); // Trigonometric functions - public static Real Acos(Real x) => T.Acos(x._value); + public static Real Acos(Real x) => Math.Acos(x._value); - public static Real Asin(Real x) => T.Asin(x._value); + public static Real Asin(Real x) => Math.Asin(x._value); - public static Real Atan(Real x) => T.Atan(x._value); + public static Real Atan(Real x) => Math.Atan(x._value); - public static Real Atan2(Real y, Real x) => T.Atan2(y._value, x._value); + public static Real Atan2(Real y, Real x) => Math.Atan2(y._value, x._value); - public static Real Cos(Real x) => T.Cos(x._value); + public static Real Cos(Real x) => Math.Cos(x._value); - public static Real Sin(Real x) => T.Sin(x._value); + public static Real Sin(Real x) => Math.Sin(x._value); - public static Real Tan(Real x) => T.Tan(x._value); + public static Real Tan(Real x) => Math.Tan(x._value); // // Implicit operators // - public static implicit operator Real(T x) => new(x); + public static implicit operator Real(double x) => new(x); } diff --git a/src/Mathematics.NET/Mathematics.NET.csproj b/src/Mathematics.NET/Mathematics.NET.csproj index ac72bf03..df134626 100644 --- a/src/Mathematics.NET/Mathematics.NET.csproj +++ b/src/Mathematics.NET/Mathematics.NET.csproj @@ -2,6 +2,7 @@ net7.0 + true enable enable x64 @@ -34,6 +35,10 @@ + + + + diff --git a/src/Mathematics.NET/Math.cs b/src/Mathematics.NET/Mathematics.cs similarity index 68% rename from src/Mathematics.NET/Math.cs rename to src/Mathematics.NET/Mathematics.cs index ca4b74d1..11f90f32 100644 --- a/src/Mathematics.NET/Math.cs +++ b/src/Mathematics.NET/Mathematics.cs @@ -1,4 +1,4 @@ -// +// // Mathematics.NET // https://github.com/HamletTanyavong/Mathematics.NET // @@ -25,67 +25,72 @@ // SOFTWARE. // -using System.Numerics; using Mathematics.NET.Core; namespace Mathematics.NET; /// Provides Mathematics.NET functionality -/// A type that implements and /// /// This class contains constants and static methods for general mathematical purposes. Complicated mathematical operations may be found in their respective classes, and a list of all available operations can be found in the documentation. /// -public static class Math where T : IFloatingPointIeee754, IMinMaxValue +public static class Mathematics { // // Constants // /// Represents the imaginary unit, $ i $ - public static Complex Im => new(Real.Zero, Real.One); + public static ComplexNumber Im => new(Real.Zero, Real.One); /// - public static Real E => Real.E; + public static Real E => Real.E; /// - public static Real Pi => Real.Pi; + public static Real Pi => Real.Pi; /// - public static Real PiOverTwo => Real.PiOverTwo; + public static Real PiOverTwo => Real.PiOverTwo; /// - public static Real PiSquared => Real.PiSquared; + public static Real PiSquared => Real.PiSquared; /// - public static Real Tau => Real.Tau; + public static Real Tau => Real.Tau; /// - public static Real EulerMascheroni => Real.EulerMascheroni; + public static Real EulerMascheroni => Real.EulerMascheroni; /// - public static Real GoldenRatio => Real.GoldenRatio; + public static Real GoldenRatio => Real.GoldenRatio; /// - public static Real Ln2 => Real.Ln2; + public static Real Ln2 => Real.Ln2; /// - public static Real Ln10 => Real.Ln10; + public static Real Ln10 => Real.Ln10; /// - public static Real Sqrt2 => Real.Sqrt2; + public static Real Sqrt2 => Real.Sqrt2; /// - public static Real Sqrt3 => Real.Sqrt3; + public static Real Sqrt3 => Real.Sqrt3; /// - public static Real Sqrt5 => Real.Sqrt5; + public static Real Sqrt5 => Real.Sqrt5; /// - public static Real ZetaOf2 => Real.ZetaOf2; + public static Real ZetaOf2 => Real.ZetaOf2; /// - public static Real ZetaOf3 => Real.ZetaOf3; + public static Real ZetaOf3 => Real.ZetaOf3; /// - public static Real ZetaOf4 => Real.ZetaOf4; + public static Real ZetaOf4 => Real.ZetaOf4; + + // + // Methods + // + + // TODO: Account for fractional derivatives + public static Real Dif(Func function, (Real X, int N) args) => throw new NotImplementedException(); } diff --git a/tests/Mathematics.NET.Benchmarks/Core/Complex/ComplexDivisionBenchmarks.cs b/tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/ComplexDivisionBenchmarks.cs similarity index 82% rename from tests/Mathematics.NET.Benchmarks/Core/Complex/ComplexDivisionBenchmarks.cs rename to tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/ComplexDivisionBenchmarks.cs index c2e32c77..c8a783c4 100644 --- a/tests/Mathematics.NET.Benchmarks/Core/Complex/ComplexDivisionBenchmarks.cs +++ b/tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/ComplexDivisionBenchmarks.cs @@ -25,16 +25,16 @@ // SOFTWARE. // -namespace Mathematics.NET.Benchmarks.Core.Complex; +namespace Mathematics.NET.Benchmarks.Core.ComplexNumberBenchmarks; [MemoryDiagnoser] public class ComplexDivisionBenchmarks { - public Complex Z { get; set; } - public Complex W { get; set; } + public ComplexNumber Z { get; set; } + public ComplexNumber W { get; set; } - public System.Numerics.Complex X { get; set; } - public System.Numerics.Complex Y { get; set; } + public Complex X { get; set; } + public Complex Y { get; set; } [GlobalSetup] public void GlobalSetup() @@ -47,13 +47,13 @@ public void GlobalSetup() } [Benchmark(Baseline = true)] - public System.Numerics.Complex SystemDivision() + public Complex SystemDivision() { return X / Y; } [Benchmark] - public Complex ComplexDivision_WithAggressiveInlining() + public ComplexNumber ComplexDivision_WithAggressiveInlining() { return Z / W; } diff --git a/tests/Mathematics.NET.Benchmarks/Core/Complex/ComplexTrigonometryBenchmarks.cs b/tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/ComplexTrigonometryBenchmarks.cs similarity index 69% rename from tests/Mathematics.NET.Benchmarks/Core/Complex/ComplexTrigonometryBenchmarks.cs rename to tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/ComplexTrigonometryBenchmarks.cs index 8474df8a..07097a38 100644 --- a/tests/Mathematics.NET.Benchmarks/Core/Complex/ComplexTrigonometryBenchmarks.cs +++ b/tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/ComplexTrigonometryBenchmarks.cs @@ -25,48 +25,48 @@ // SOFTWARE. // -namespace Mathematics.NET.Benchmarks.Core.Complex; +namespace Mathematics.NET.Benchmarks.Core.ComplexNumberBenchmarks; [MemoryDiagnoser] [RankColumn] [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class ComplexTrigonometryBenchmarks { - public Complex Z { get; set; } - public Complex ImOverTwo { get; set; } + public ComplexNumber Z { get; set; } + public ComplexNumber ImOverTwo { get; set; } - public System.Numerics.Complex W { get; set; } + public Complex W { get; set; } [GlobalSetup] public void GlobalSetup() { Z = new(1.23, 2.34); - ImOverTwo = Math.Im / Complex.Two; + ImOverTwo = Mathematics.Im / ComplexNumber.Two; W = new(1.23, 2.34); } [Benchmark(Baseline = true)] - public System.Numerics.Complex Atan_System() + public Complex Atan_System() { - return System.Numerics.Complex.Atan(W); + return Complex.Atan(W); } [Benchmark] - public Complex Atan_MathNET() + public ComplexNumber Atan_MathNET() { - return Complex.Atan(Z); + return ComplexNumber.Atan(Z); } //[Benchmark] - public Complex Atan_WithoutConstImOverTwo() + public ComplexNumber Atan_WithoutConstImOverTwo() { - return Math.Im / Complex.Two * Complex.Ln((Math.Im + Z) / (Math.Im - Z)); + return Mathematics.Im / ComplexNumber.Two * ComplexNumber.Ln((Mathematics.Im + Z) / (Mathematics.Im - Z)); } //[Benchmark] - public Complex Atan_WithConstImOverTwo() + public ComplexNumber Atan_WithConstImOverTwo() { - return ImOverTwo * Complex.Ln((Math.Im + Z) / (Math.Im - Z)); + return ImOverTwo * ComplexNumber.Ln((Mathematics.Im + Z) / (Mathematics.Im - Z)); } } diff --git a/tests/Mathematics.NET.Benchmarks/Core/Complex/SystemComplexAbsVsComplexAbsBenchmarks.cs b/tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/SystemComplexAbsVsComplexAbsBenchmarks.cs similarity index 83% rename from tests/Mathematics.NET.Benchmarks/Core/Complex/SystemComplexAbsVsComplexAbsBenchmarks.cs rename to tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/SystemComplexAbsVsComplexAbsBenchmarks.cs index c31b0bd0..54b2bcfa 100644 --- a/tests/Mathematics.NET.Benchmarks/Core/Complex/SystemComplexAbsVsComplexAbsBenchmarks.cs +++ b/tests/Mathematics.NET.Benchmarks/Core/ComplexNumberBenchmarks/SystemComplexAbsVsComplexAbsBenchmarks.cs @@ -25,15 +25,15 @@ // SOFTWARE. // -namespace Mathematics.NET.Benchmarks.Core.Complex; +namespace Mathematics.NET.Benchmarks.Core.ComplexNumberBenchmarks; [MemoryDiagnoser] [RankColumn] [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class SystemComplexAbsVsComplexAbsBenchmarks { - public System.Numerics.Complex Z { get; set; } - public Complex W { get; set; } + public Complex Z { get; set; } + public ComplexNumber W { get; set; } [GlobalSetup] public void GlobalSetup() @@ -43,14 +43,14 @@ public void GlobalSetup() } [Benchmark(Baseline = true)] - public Real SystemComplexAbs() + public Real SystemComplexAbs() { - return System.Numerics.Complex.Abs(Z); + return Complex.Abs(Z); } [Benchmark] - public Complex ComplexAbs() + public ComplexNumber ComplexAbs() { - return Complex.Abs(W); + return ComplexNumber.Abs(W); } } diff --git a/tests/Mathematics.NET.Benchmarks/Core/Real/RealvsDouble.cs b/tests/Mathematics.NET.Benchmarks/Core/RealNumberBenchmarks/RealvsDouble.cs similarity index 91% rename from tests/Mathematics.NET.Benchmarks/Core/Real/RealvsDouble.cs rename to tests/Mathematics.NET.Benchmarks/Core/RealNumberBenchmarks/RealvsDouble.cs index 4dacfc77..19309799 100644 --- a/tests/Mathematics.NET.Benchmarks/Core/Real/RealvsDouble.cs +++ b/tests/Mathematics.NET.Benchmarks/Core/RealNumberBenchmarks/RealvsDouble.cs @@ -25,14 +25,14 @@ // SOFTWARE. // -namespace Mathematics.NET.Benchmarks.Core.Real; +namespace Mathematics.NET.Benchmarks.Core.RealNumberBenchmarks; [MemoryDiagnoser] [RankColumn] [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class RealvsDouble { - public Real XReal { get; set; } + public Real XReal { get; set; } public double XDouble { get; set; } [GlobalSetup] @@ -54,9 +54,9 @@ public double AdditionsWithDouble() } [Benchmark] - public Real AdditionsWithReal() + public Real AdditionsWithReal() { - Real result = 0.0; + Real result = 0.0; for (int i = 0; i < 100_000; i++) { result += XReal; diff --git a/tests/Mathematics.NET.Benchmarks/Mathematics.NET.Benchmarks.csproj b/tests/Mathematics.NET.Benchmarks/Mathematics.NET.Benchmarks.csproj index 7f898c95..da1b10e2 100644 --- a/tests/Mathematics.NET.Benchmarks/Mathematics.NET.Benchmarks.csproj +++ b/tests/Mathematics.NET.Benchmarks/Mathematics.NET.Benchmarks.csproj @@ -9,7 +9,7 @@ - + @@ -20,6 +20,7 @@ + diff --git a/tests/Mathematics.NET.Benchmarks/Program.cs b/tests/Mathematics.NET.Benchmarks/Program.cs index caf4c3b9..b151bf15 100644 --- a/tests/Mathematics.NET.Benchmarks/Program.cs +++ b/tests/Mathematics.NET.Benchmarks/Program.cs @@ -28,7 +28,7 @@ #if RELEASE using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; -using Mathematics.NET.Benchmarks.Core.Complex; +using Mathematics.NET.Benchmarks.Core.ComplexNumberBenchmarks; using Mathematics.NET.Benchmarks.Core.Real; var benchmarkSwitcher = new BenchmarkSwitcher(new[] diff --git a/tests/Mathematics.NET.Tests/Core/ComplexTests.cs b/tests/Mathematics.NET.Tests/Core/ComplexTests.cs index 00398916..877b958d 100644 --- a/tests/Mathematics.NET.Tests/Core/ComplexTests.cs +++ b/tests/Mathematics.NET.Tests/Core/ComplexTests.cs @@ -33,11 +33,11 @@ public sealed class ComplexTests { [TestMethod] [DataRow(1.23, 2.34, 1.114564084931578, -1.686112230652994)] - public void Acos_ComplexOfDouble_ReturnsComplexOfDouble(double inReal, double inImaginary, double expectedRe, double expectedIm) + public void Acos_Complex_ReturnsComplex(double inReal, double inImaginary, double expectedRe, double expectedIm) { - Complex input = new(inReal, inImaginary); + ComplexNumber input = new(inReal, inImaginary); - var actualResult = Complex.Acos(input); + var actualResult = ComplexNumber.Acos(input); var actualRe = actualResult.Re.Value; var actualIm = actualResult.Im.Value; @@ -47,11 +47,11 @@ public void Acos_ComplexOfDouble_ReturnsComplexOfDouble(double inReal, double in [TestMethod] [DataRow(1.23, 2.34, 4.562322418633185e-1, 1.686112230652994)] - public void Asin_ComplexOfDouble_ReturnsComplexOfDouble(double inReal, double inImaginary, double expectedRe, double expectedIm) + public void Asin_Complex_ReturnsComplex(double inReal, double inImaginary, double expectedRe, double expectedIm) { - Complex input = new(inReal, inImaginary); + ComplexNumber input = new(inReal, inImaginary); - var actualResult = Complex.Asin(input); + var actualResult = ComplexNumber.Asin(input); var actualRe = actualResult.Re.Value; var actualIm = actualResult.Im.Value; @@ -61,11 +61,11 @@ public void Asin_ComplexOfDouble_ReturnsComplexOfDouble(double inReal, double in [TestMethod] [DataRow(1.23, 2.34, 1.37591078602063, 3.356559207392595e-1)] - public void Atan_ComplexOfDouble_ReturnsComplexOfDouble(double inReal, double inImaginary, double expectedRe, double expectedIm) + public void Atan_Complex_ReturnsComplex(double inReal, double inImaginary, double expectedRe, double expectedIm) { - Complex input = new(inReal, inImaginary); + ComplexNumber input = new(inReal, inImaginary); - var actualResult = Complex.Atan(input); + var actualResult = ComplexNumber.Atan(input); var actualRe = actualResult.Re.Value; var actualIm = actualResult.Im.Value; @@ -75,12 +75,12 @@ public void Atan_ComplexOfDouble_ReturnsComplexOfDouble(double inReal, double in [TestMethod] [DataRow(1.2, 2.3, 1.2, -2.3)] - public void Conjugate_ComplexOfDouble_ReturnsConjugate(double inReal, double inImaginary, double outReal, double outImaginary) + public void Conjugate_Complex_ReturnsConjugate(double inReal, double inImaginary, double outReal, double outImaginary) { - Complex input = new(inReal, inImaginary); - Complex expected = new(outReal, outImaginary); + ComplexNumber input = new(inReal, inImaginary); + ComplexNumber expected = new(outReal, outImaginary); - var actual = Complex.Conjugate(input); + var actual = ComplexNumber.Conjugate(input); Assert.AreEqual(expected, actual); } @@ -90,10 +90,10 @@ public void Conjugate_ComplexOfDouble_ReturnsConjugate(double inReal, double inI [DataRow(1, 2, 3, 4, 0.44, 0.08)] [DataRow(2, 1.5, 5, 3, 0.4264705882352942, 0.04411764705882351)] [DataRow(5, 3.5, 7, 0, 0.7142857142857142, 0.5)] - public void Division_ComplexOfDoubles_ReturnsComplexOfDouble(double dividendRe, double dividendIm, double divisorRe, double divisorIm, double expectedRe, double expectedIm) + public void Division_Complexs_ReturnsComplex(double dividendRe, double dividendIm, double divisorRe, double divisorIm, double expectedRe, double expectedIm) { - Complex dividend = new(dividendRe, dividendIm); - Complex divisor = new(divisorRe, divisorIm); + ComplexNumber dividend = new(dividendRe, dividendIm); + ComplexNumber divisor = new(divisorRe, divisorIm); var actualResult = dividend / divisor; var actualRe = actualResult.Re.Value; @@ -105,9 +105,9 @@ public void Division_ComplexOfDoubles_ReturnsComplexOfDouble(double dividendRe, [TestMethod] [DataRow(3, 4, 5)] - public void Magnitude_ComplexOfDouble_ReturnsMagnitude(double inReal, double inImaginary, double expected) + public void Magnitude_Complex_ReturnsMagnitude(double inReal, double inImaginary, double expected) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); var actual = z.Magnitude.Value; @@ -119,9 +119,9 @@ public void Magnitude_ComplexOfDouble_ReturnsMagnitude(double inReal, double inI [DataRow(1, -1, -Math.PI / 2, 0)] [DataRow(-1, 1, Math.PI / 2, Math.PI)] [DataRow(-1, -1, -Math.PI, -Math.PI / 2)] - public void Phase_ComplexOfDouble_ReturnsAngleInCorrectQuadrant(double inReal, double inImaginary, double expectedMin, double expectedMax) + public void Phase_Complex_ReturnsAngleInCorrectQuadrant(double inReal, double inImaginary, double expectedMin, double expectedMax) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); var actual = z.Phase.Value; @@ -131,12 +131,12 @@ public void Phase_ComplexOfDouble_ReturnsAngleInCorrectQuadrant(double inReal, d [TestMethod] [DataRow(2, 0, 0.5, 0)] [DataRow(1.5, 2.5, 1.76470588235294117e-1, -2.94117647058823529e-1)] - public void Reciprocate_ComplexOfDouble_ReturnsReciprocal(double inReal, double inImaginary, double expectedRe, double expectedIm) + public void Reciprocate_Complex_ReturnsReciprocal(double inReal, double inImaginary, double expectedRe, double expectedIm) { - Complex z = new(inReal, inImaginary); - Complex expected = new(expectedRe, expectedIm); + ComplexNumber z = new(inReal, inImaginary); + ComplexNumber expected = new(expectedRe, expectedIm); - var actual = Complex.Reciprocate(z); + var actual = ComplexNumber.Reciprocate(z); Assert.AreEqual(expected, actual); } @@ -146,9 +146,9 @@ public void Reciprocate_ComplexOfDouble_ReturnsReciprocal(double inReal, double [DataRow(24.56, 9.23, "ALL", "(24.56, 9.23)")] [DataRow(62.151, 27, "RE", "62.151")] [DataRow(7.345, 124.841, "IM", "124.841")] - public void ToString_ComplexOfDouble_ReturnsString(double inReal, double inImaginary, string? format, string expected) + public void ToString_Complex_ReturnsString(double inReal, double inImaginary, string? format, string expected) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); var actual = z.ToString(format, null); @@ -158,9 +158,9 @@ public void ToString_ComplexOfDouble_ReturnsString(double inReal, double inImagi [TestMethod] [DataRow(0, 0, 6)] [DataRow(1.23, 2.34, 12)] - public void TryFormat_ComplexOfDoubleWithAdequateDestinationLength_ReturnsTrue(double inReal, double inImaginary, int length) + public void TryFormat_ComplexWithAdequateDestinationLength_ReturnsTrue(double inReal, double inImaginary, int length) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); Span span = new char[length]; var actual = z.TryFormat(span, out int _, null, null); @@ -171,9 +171,9 @@ public void TryFormat_ComplexOfDoubleWithAdequateDestinationLength_ReturnsTrue(d [TestMethod] [DataRow(0, 0, 5)] [DataRow(1.23, 2.34, 11)] - public void TryFormat_ComplexOfDoubleWithInadequateDestinationLength_ReturnsFalse(double inReal, double inImaginary, int length) + public void TryFormat_ComplexWithInadequateDestinationLength_ReturnsFalse(double inReal, double inImaginary, int length) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); Span span = new char[length]; var actual = z.TryFormat(span, out int _, null, null); @@ -186,11 +186,11 @@ public void TryFormat_ComplexOfDoubleWithInadequateDestinationLength_ReturnsFals [DataRow(" (0, 0) ", 0, 0)] [DataRow("(1.23, 3.456)", 1.23, 3.456)] [DataRow("( 1.23 , 3.45 )", 1.23, 3.45)] - public void TryParse_SpanOfChar_ReturnsComplexOfDouble(string s, double expectedRe, double expectedIm) + public void TryParse_SpanOfChar_ReturnsComplex(string s, double expectedRe, double expectedIm) { - Complex expected = new(expectedRe, expectedIm); + ComplexNumber expected = new(expectedRe, expectedIm); - _ = Complex.TryParse(s.AsSpan(), null, out Complex actual); + _ = ComplexNumber.TryParse(s.AsSpan(), null, out ComplexNumber actual); Assert.AreEqual(expected, actual); } @@ -202,7 +202,7 @@ public void TryParse_SpanOfChar_ReturnsComplexOfDouble(string s, double expected [DataRow("(0,0")] public void TryParse_SpanOfChar_ReturnsFalse(string s) { - var actual = Complex.TryParse(s.AsSpan(), null, out _); + var actual = ComplexNumber.TryParse(s.AsSpan(), null, out _); Assert.IsFalse(actual); } @@ -219,9 +219,9 @@ public void TryParse_SpanOfChar_ReturnsFalse(string s) [DataRow(1.2345, 0, 6, 1)] [DataRow(1.2345, 0, 7, 7)] [DataRow(1.2345, 0, 8, 8)] - public void TryFormat_ComplexOfDoubleWithInadequateDestinationLength_ReturnsCorrectNumberOfCharsWritten(double inReal, double inImaginary, int length, int expected) + public void TryFormat_ComplexWithInadequateDestinationLength_ReturnsCorrectNumberOfCharsWritten(double inReal, double inImaginary, int length, int expected) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); Span span = new char[length]; _ = z.TryFormat(span, out int actual, null, null); @@ -234,9 +234,9 @@ public void TryFormat_ComplexOfDoubleWithInadequateDestinationLength_ReturnsCorr [DataRow(1.23, 3.4567, 14, "ALL", "(1.23, 3.4567)")] [DataRow(4.537, 2.3, 5, "RE", "4.537")] [DataRow(1.2, 7, 1, "IM", "7")] - public void TryFormat_ComplexOfDouble_ReturnsSpanOfCharacters(double inReal, double inImaginary, int length, string? format, string expected) + public void TryFormat_Complex_ReturnsSpanOfCharacters(double inReal, double inImaginary, int length, string? format, string expected) { - Complex z = new(inReal, inImaginary); + ComplexNumber z = new(inReal, inImaginary); Span actual = new char[length]; _ = z.TryFormat(actual, out int _, format, null); diff --git a/tests/Mathematics.NET.Tests/Core/RationalTests.cs b/tests/Mathematics.NET.Tests/Core/RationalTests.cs index 83a127e1..3e2ee94e 100644 --- a/tests/Mathematics.NET.Tests/Core/RationalTests.cs +++ b/tests/Mathematics.NET.Tests/Core/RationalTests.cs @@ -33,11 +33,11 @@ public sealed class RationalTests { [TestMethod] [DataRow(2, 4, 5, 3, 13, 6)] - public void Add_TwoRationalsOfIntAndDouble_ReturnsReducedSum(int inANum, int inADen, int inBNum, int inBDen, int expectedNum, int expectedDen) + public void Add_TwoRationalsOfInt_ReturnsReducedSum(int inANum, int inADen, int inBNum, int inBDen, int expectedNum, int expectedDen) { - Rational x = new(inANum, inADen); - Rational y = new(inBNum, inBDen); - Rational expected = new(expectedNum, expectedDen); + Rational x = new(inANum, inADen); + Rational y = new(inBNum, inBDen); + Rational expected = new(expectedNum, expectedDen); var actual = x + y; @@ -45,20 +45,20 @@ public void Add_TwoRationalsOfIntAndDouble_ReturnsReducedSum(int inANum, int inA } [TestMethod] - public void Divide_RationalOfIntAndDoubleByZero_ReturnsNaN() + public void Divide_RationalOfIntByZero_ReturnsNaN() { - var actual = Rational.One / Rational.Zero; + var actual = Rational.One / Rational.Zero; - Assert.AreEqual(Rational.NaN, actual); + Assert.AreEqual(Rational.NaN, actual); } [TestMethod] [DataRow(2, 3, 4, 8, 1, 3)] - public void Multiply_TwoRationalsOfIntAndDouble_ReturnsReducedProduct(int inANum, int inADen, int inBNum, int inBDen, int expectedNum, int expectedDen) + public void Multiply_TwoRationalsOfInt_ReturnsReducedProduct(int inANum, int inADen, int inBNum, int inBDen, int expectedNum, int expectedDen) { - Rational x = new(inANum, inADen); - Rational y = new(inBNum, inBDen); - Rational expected = new(expectedNum, expectedDen); + Rational x = new(inANum, inADen); + Rational y = new(inBNum, inBDen); + Rational expected = new(expectedNum, expectedDen); var actual = x * y; @@ -67,10 +67,10 @@ public void Multiply_TwoRationalsOfIntAndDouble_ReturnsReducedProduct(int inANum [TestMethod] [DataRow(6, 8, 3, 4)] - public void Reduce_RationalOfIntAndDouble_ReturnsReducedFraction(int inNum, int inDen, int expectedNum, int expectedDen) + public void Reduce_RationalOfInt_ReturnsReducedFraction(int inNum, int inDen, int expectedNum, int expectedDen) { - Rational p = new(inNum, inDen); - Rational expected = new(expectedNum, expectedDen); + Rational p = new(inNum, inDen); + Rational expected = new(expectedNum, expectedDen); var actual = p.Reduce(); @@ -79,9 +79,9 @@ public void Reduce_RationalOfIntAndDouble_ReturnsReducedFraction(int inNum, int [TestMethod] [DataRow(3, 4, 7)] - public void TryFormat_RationalOfIntAndDoubleWithAdequateDestinationLength_ReturnsTrue(int inNum, int inDen, int length) + public void TryFormat_RationalOfIntWithAdequateDestinationLength_ReturnsTrue(int inNum, int inDen, int length) { - Rational p = new(inNum, inDen); + Rational p = new(inNum, inDen); Span span = new char[length]; var actual = p.TryFormat(span, out int _, null, null);