diff --git a/MathEngine/EngineTests/AST Tests/Nodes/NodeFactoryTests.cs b/MathEngine/EngineTests/AST Tests/Nodes/NodeFactoryTests.cs index 93e3491..c644973 100644 --- a/MathEngine/EngineTests/AST Tests/Nodes/NodeFactoryTests.cs +++ b/MathEngine/EngineTests/AST Tests/Nodes/NodeFactoryTests.cs @@ -4,6 +4,7 @@ using MathEngine.AST.Nodes; using MathEngine.Tokenizer; using static MathEngine.Tokenizer.Token; +using MathEngine.Types; namespace EngineTests.Parser_Tests.Nodes { @@ -18,8 +19,8 @@ namespace EngineTests.Parser_Tests.Nodes [Fact] public void TestNodeFactoryBinaryNodesOnDefinedOperations() { - NumericNode node1 = new(200); - NumericNode node2 = new(100); + NumericNode node1 = new(new DecimalValue(200)); + NumericNode node2 = new(new DecimalValue(100)); Token plus = Token.Plus; Token minus = Token.Minus; diff --git a/MathEngine/MathEngine/AST/Nodes/BaseNode.cs b/MathEngine/MathEngine/AST/Nodes/BaseNode.cs index f112add..d58f61f 100644 --- a/MathEngine/MathEngine/AST/Nodes/BaseNode.cs +++ b/MathEngine/MathEngine/AST/Nodes/BaseNode.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; - -namespace MathEngine.AST.Nodes +namespace MathEngine.AST.Nodes { /// /// Abstract class representing a Node in a Tree structure diff --git a/MathEngine/MathEngine/AST/Nodes/FunctionNode.cs b/MathEngine/MathEngine/AST/Nodes/FunctionNode.cs index b9a1ca7..cf31b58 100644 --- a/MathEngine/MathEngine/AST/Nodes/FunctionNode.cs +++ b/MathEngine/MathEngine/AST/Nodes/FunctionNode.cs @@ -1,21 +1,23 @@ -using System; +using MathEngine.Types.Interfaces; +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; +using static MathEngine.Functions.BuiltIns.BuiltIns; namespace MathEngine.AST.Nodes { /// /// Represents a Tree node that stores a function /// - internal class FunctionNode : BaseNode where T : INumber + internal class FunctionNode : BaseNode { - Action func; - List> args; + private FunctionWrapper func; + private INumericNode[] args; - public FunctionNode(Action Function, List> arguments) + public FunctionNode(FunctionWrapper Function, INumericNode[] arguments) { func = Function; args = arguments; @@ -23,7 +25,7 @@ namespace MathEngine.AST.Nodes public override BaseNode Evaluate() { - throw new NotImplementedException(); + return func(args); } } } diff --git a/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs b/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs index d7115cd..27fccd4 100644 --- a/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs +++ b/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs @@ -1,5 +1,9 @@ -using MathEngine.Tokenizer; - +using MathEngine.Functions.BuiltIns; +using MathEngine.Tokenizer; +using MathEngine.Types; +using MathEngine.Types.Interfaces; +using System.Runtime.Intrinsics.X86; +using Xunit.Sdk; using static MathEngine.Tokenizer.Token; namespace MathEngine.AST.Nodes @@ -32,7 +36,7 @@ namespace MathEngine.AST.Nodes return new BinaryNode(LeftBranch, RightBranch, (a, b) => a / b); case TokenType.Exponentiation: throw new NotImplementedException("Exponentiation is not supported at this time!"); - return new BinaryNode(LeftBranch, RightBranch, (a, b) => a ^ b); + // return new BinaryNode(LeftBranch, RightBranch, (a, b) => a ^ b); default: throw new NotImplementedException("Attempted to create a BinaryNode with an invalid operation!"); } @@ -46,23 +50,25 @@ namespace MathEngine.AST.Nodes /// public static BaseNode CreateFunctionNode(Token FunctionToken, Stack ArgumentStack) { - throw new NotImplementedException("Not implemented at this time"); - /* Check that the function exists, if it does we then check that the number of arguments - * for the function is are avaliable on the ArgumentStack. If so we can construct the Node. - * Otherwise, either the function does not exist or there is an argument mismatch */ - /*try - { - functionAction = GetFunction(FunctionToken, out int functionArity); - if (functionArity >= ArgumentStack.Count) - { - throw new ArgumentException("Number of required arguments is greater than avaliable arugments"); - } - } - catch (Exception) - { + int argc = (int)FunctionToken.Arity; - throw; - }*/ + INumericNode[] argsv = new INumericNode[argc]; + + for (int i = argc; i > 0; i--) + { + // TODO: Update so either INumeric or function nodes are allowed + // this will enable nested functions + if (ArgumentStack.Peek() is INumericNode numericNode) + { + argsv[i - 1] = numericNode; + ArgumentStack.Pop(); + } + else + { + throw new InvalidOperationException("Function arguments implement INumericNode."); + } + } + return new FunctionNode(BuiltIns.GetFunction(FunctionToken.Value), argsv); } /// @@ -81,7 +87,7 @@ namespace MathEngine.AST.Nodes { throw new ArgumentException("Failure to parse number"); } - return new NumericNode(res); + return new NumericallyOrderableNode(new DecimalValue(res)); } } diff --git a/MathEngine/MathEngine/AST/Nodes/NumericNode.cs b/MathEngine/MathEngine/AST/Nodes/NumericNode.cs index c00c957..7187fa3 100644 --- a/MathEngine/MathEngine/AST/Nodes/NumericNode.cs +++ b/MathEngine/MathEngine/AST/Nodes/NumericNode.cs @@ -1,87 +1,115 @@ -using System.Numerics; +using MathEngine.Types.Interfaces; +using System.Numerics; +using System.Runtime.CompilerServices; namespace MathEngine.AST.Nodes { /// /// Represents a Tree node that can store a numeric value /// - internal class NumericNode : BaseNode where T : INumber + internal class NumericNode: BaseNode, INumericNode, ITrigonometricNode where T : struct, INumeric { - private readonly decimal Value; + protected readonly T _value; + + Type INumericNode.NumericType + { + get + { + return typeof(T); + } + } /// /// Initialises a new instance of a NumericNode with a given Token /// /// The token for the nodes value - public NumericNode(decimal value) + public NumericNode(T value) { Children = null; - Value = value; + _value = value; } protected override BaseNode Add(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode rhs) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value + rhs.Value); + return new NumericNode(_value.Add(rhs._value)); } throw new InvalidOperationException("Attempted Invalid operation"); } protected override BaseNode Subtract(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value - rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(_value.Subtract(rhs._value)); } throw new InvalidOperationException("Attempted Invalid operation"); } protected override BaseNode Multiply(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value * rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(_value.Multiply(rhs._value)); } throw new InvalidOperationException("Attempted Invalid operation"); } protected override BaseNode Divide(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value / rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(_value.Divide(rhs._value)); } throw new InvalidOperationException("Attempted Invalid operation"); } - protected override BaseNode Exponentiate(BaseNode otherNode) - { - if (otherNode is NumericNode) - { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode((decimal)Math.Pow((double)Value, (double)rhs.Value)); - } - throw new InvalidOperationException("Attempted Invalid operation"); - } - - /// - /// Evaluates the Numeric Node by simply returning the current instance - /// - /// public override BaseNode Evaluate() { return this; } - public override string ToString() + internal T GetValue() { - return Value.ToString(); + return _value; } + void INumericNode.CopyTo(ref TDest destination) + { + if (typeof(TDest) == typeof(T)) + { + destination = Unsafe.As(ref Unsafe.AsRef(in _value)); + } + else + { + throw new InvalidOperationException($"Cannot copy {typeof(T)} to {typeof(TDest)}"); + } + } + + public INumericNode Sin() + { + if (_value is ITrigonometric trig) + return new NumericNode(trig.Sin()); + throw new NotImplementedException(); + + } + + public INumericNode Cos() + { + if (_value is ITrigonometric trig) + return new NumericNode(trig.Cos()); + throw new NotImplementedException(); + } + + public INumericNode Tan() + { + if (_value is ITrigonometric trig) + return new NumericNode(trig.Tan()); + throw new NotImplementedException(); + } } } diff --git a/MathEngine/MathEngine/AST/Nodes/NumericallyOrderableNode.cs b/MathEngine/MathEngine/AST/Nodes/NumericallyOrderableNode.cs new file mode 100644 index 0000000..1e03ac4 --- /dev/null +++ b/MathEngine/MathEngine/AST/Nodes/NumericallyOrderableNode.cs @@ -0,0 +1,55 @@ +using MathEngine.Types.Interfaces; + +namespace MathEngine.AST.Nodes +{ + internal class NumericallyOrderableNode : NumericNode, INumericallyOrderable where T : struct, INumericallyOrderable + { + public NumericallyOrderableNode(T value) : base(value) + { + + } + + int INumericallyOrderable.CompareTo(INumericNode other) + { + if (other is NumericallyOrderableNode rhs) + { + return this._value.CompareTo(rhs._value); + } + throw new Exception(); + } + + bool INumericallyOrderable.GreaterThan(INumericNode other) + { + throw new NotImplementedException(); + } + + bool INumericallyOrderable.GreaterThanOrEqual(INumericNode other) + { + throw new NotImplementedException(); + } + + bool INumericallyOrderable.LessThan(INumericNode other) + { + throw new NotImplementedException(); + } + + bool INumericallyOrderable.LessThanOrEqual(INumericNode other) + { + throw new NotImplementedException(); + } + + INumericallyOrderable INumericallyOrderable.Max(INumericNode other) + { + if (other is NumericallyOrderableNode rhs) + return _value.GreaterThanOrEqual(rhs._value) ? this : rhs; + + throw new InvalidOperationException("Cannot compare incompatible numeric node types"); + + } + + INumericallyOrderable INumericallyOrderable.Min(INumericNode other) + { + throw new NotImplementedException(); + } + } +} diff --git a/MathEngine/MathEngine/Expression/Expression.cs b/MathEngine/MathEngine/Expression/Expression.cs index ecb0106..54044fe 100644 --- a/MathEngine/MathEngine/Expression/Expression.cs +++ b/MathEngine/MathEngine/Expression/Expression.cs @@ -1,5 +1,6 @@ using MathEngine.AST; using MathEngine.AST.Nodes; +using MathEngine.Types; namespace MathEngine.Expression { diff --git a/MathEngine/MathEngine/Functions/BuiltIns/BuiltIns.cs b/MathEngine/MathEngine/Functions/BuiltIns/BuiltIns.cs new file mode 100644 index 0000000..e02f9b5 --- /dev/null +++ b/MathEngine/MathEngine/Functions/BuiltIns/BuiltIns.cs @@ -0,0 +1,105 @@ +using MathEngine.AST.Nodes; +using MathEngine.Types; +using MathEngine.Types.Interfaces; +using System; +using System.Collections.Generic; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace MathEngine.Functions.BuiltIns +{ + internal class BuiltIns + { + // Uniform wrapper type: takes decimal[] and returns result via callback + public delegate BaseNode FunctionWrapper(INumericNode[] args); + + // Dictionary storing the wrapped functions + private static readonly Dictionary _builtIns = new() + { + { "sin", Wrap(SinImply)}, + { "cos", Wrap(CosImply)}, + { "tan", Wrap(TanImply)}, + { "max", Wrap(MaxImply)}, + }; + + + + // Wrap a Func into FunctionWrapper + private static FunctionWrapper Wrap(Func func) + { + return args => (BaseNode)func(args); + } + + + public static bool IsFunction(string name) + { + return _builtIns.ContainsKey(name); + } + + // Retrieve the wrapper for a function + public static FunctionWrapper GetFunction(string name) + { + if (!_builtIns.TryGetValue(name, out var func)) + throw new Exception($"Unknown function: {name}"); + return func; + } + + + private static INumericNode SinImply(INumericNode[] span) + { + INumericNode arg = span[0]; + if (arg is ITrigonometricNode trigNode) + { + return trigNode.Sin(); + } + + throw new InvalidOperationException("Argument does not support Sin()"); + } + + private static INumericNode CosImply(INumericNode[] span) + { + INumericNode arg = span[0]; + if (arg is ITrigonometricNode trigNode) + { + return trigNode.Cos(); + } + + throw new InvalidOperationException("Argument does not support Sin()"); + } + + private static INumericNode TanImply(INumericNode[] span) + { + INumericNode arg = span[0]; + if (arg is ITrigonometricNode trigNode) + { + return trigNode.Tan(); + } + + throw new InvalidOperationException("Argument does not support Sin()"); + } + private static INumericNode MaxImply(INumericNode[] span) + { + var maxNode = span[0] as INumericallyOrderable + ?? throw new InvalidOperationException("Argument not orderable"); + + foreach (var node in span.Skip(1)) + { + if (node is INumericallyOrderable orderable) + { + maxNode = maxNode.Max(orderable); + } + else + { + throw new InvalidOperationException("All arguments must be orderable"); + } + } + + return maxNode; + } + + ////// Allow registering user-defined functions + ////public static void RegisterFunction(string name, Func, INumericNode> func) + ////{ + //// _builtIns[name] = Wrap(func); + ////} + } +} \ No newline at end of file diff --git a/MathEngine/MathEngine/Parser/Parser.cs b/MathEngine/MathEngine/Parser/Parser.cs index 436a300..3a7c17b 100644 --- a/MathEngine/MathEngine/Parser/Parser.cs +++ b/MathEngine/MathEngine/Parser/Parser.cs @@ -1,4 +1,5 @@ -using MathEngine.Tokenizer; +using MathEngine.Functions.BuiltIns; +using MathEngine.Tokenizer; using System.Security; using static MathEngine.Tokenizer.Token; @@ -155,8 +156,10 @@ namespace MathEngine.Parser Stack OperatorStack = new(Expression.Count/2); //The final stack to return Queue rpnQueue = new(Expression.Count); - //Stack used to hold the number of input params to a function - //Stack ArityStack = new Stack(); + + // Stack that is used to handle nested function argument counts + Stack arityStack = new(); + Token PreviousToken = Token.None; Token CurrentToken; @@ -179,8 +182,33 @@ namespace MathEngine.Parser PopOperatorsToQueue(OperatorStack, rpnQueue, CurrentToken); OperatorStack.Push(CurrentToken); break; - case TokenType.Function: - OperatorStack.Push(CurrentToken); + case TokenType.GenericIdentifier: + // few things to consider + // 1 Check built-ins + if (i + 1 < Expression.Count && Expression[i + 1].Type == TokenType.LeftParenthesis && BuiltIns.IsFunction(CurrentToken.Value)) + { + OperatorStack.Push(CurrentToken with { Type = Token.TokenType.Function }); + arityStack.Push(1); // at least one arg must be present + break; + } + // 2 check user defined functions + // TODO: Implement User definable functions + + // 3 check to see if it is a user defined variable + // TODO: Implement user definable variables + + // 4 assume it is an implict multiplication + // TODO: Split identifier into letters and insert implicit multiplication + break; + case TokenType.FunctionArgumentSeparator: + if (arityStack.Count == 0) + throw new ParserException("Unexpected function arguement separator"); + // Pop operators until the last LeftParenthesis + while (OperatorStack.Count > 0 && OperatorStack.Peek().Type != TokenType.LeftParenthesis) + rpnQueue.Enqueue(OperatorStack.Pop()); + + // increment the argument counter + arityStack.Push(arityStack.Pop() + 1); break; case TokenType.LeftParenthesis: OperatorStack.Push(CurrentToken); @@ -198,7 +226,7 @@ namespace MathEngine.Parser if (OperatorStack.Count > 0 && (OperatorStack.Peek().Type == TokenType.Function)) { - rpnQueue.Enqueue(OperatorStack.Pop()); + rpnQueue.Enqueue(OperatorStack.Pop() with { Arity = arityStack.Pop()}); } break; } diff --git a/MathEngine/MathEngine/Tokeniser/Token.cs b/MathEngine/MathEngine/Tokeniser/Token.cs index b26c1a4..534dfeb 100644 --- a/MathEngine/MathEngine/Tokeniser/Token.cs +++ b/MathEngine/MathEngine/Tokeniser/Token.cs @@ -17,6 +17,7 @@ namespace MathEngine.Tokenizer internal enum TokenType { None, + GenericIdentifier, Numeric, DecimalPoint, @@ -50,6 +51,7 @@ namespace MathEngine.Tokenizer public static readonly Token Exponentiation = new("^", TokenType.Exponentiation); public static readonly Token LeftParenthesis = new("(", TokenType.LeftParenthesis); public static readonly Token RightParenthesis = new(")", TokenType.RightParenthesis); + public static readonly Token FunctionArgumentSeparator = new(",", TokenType.FunctionArgumentSeparator); /// /// Returns true if the token represents any arithmetic operator. diff --git a/MathEngine/MathEngine/Tokeniser/Tokeniser.cs b/MathEngine/MathEngine/Tokeniser/Tokeniser.cs index 3297e10..74e289e 100644 --- a/MathEngine/MathEngine/Tokeniser/Tokeniser.cs +++ b/MathEngine/MathEngine/Tokeniser/Tokeniser.cs @@ -1,5 +1,6 @@  using MathEngine.Tokenizer; +using System.ComponentModel.Design; using static MathEngine.Tokenizer.Token; namespace MathEngine.Tokenizer @@ -24,6 +25,18 @@ namespace MathEngine.Tokenizer } + /// + /// Checks if a character represents an ASCII letter + /// + /// The character to check + /// True if represents an ASCII letter, false otherwise + + private static bool IsAsciiLetter(char c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } + + /// /// Checks if a character represents an ASCII whitespace character /// @@ -45,12 +58,22 @@ namespace MathEngine.Tokenizer return c == '.'; } + /// + /// Checks if a character represents the FunctionArgumentSeparator + /// + /// The character to check + /// True if represents the FunctionArgumentSeparator, false otherwise + private static bool IsFunctionArgumentSeparator(char c) + { + return c == ',' ? true : false; + } + /// /// Parses the to read a numeric /// /// The expression being parsed /// Indicates if the character read before calling this method was a decimal point or not - /// The current index the parser is at + /// The current index the parser is at /// A representing the numeric value read private static Token ReadNumericToken(ReadOnlySpan expression, bool at_decimal_point, ref Int32 currentIndex) { @@ -75,6 +98,10 @@ namespace MathEngine.Tokenizer ReadOnlySpan errSpan = expression.Slice(currentIndex, errIndex - currentIndex); throw new TokenizerException($"Syntax error: The number {errSpan.ToString()} has multiple decimal point when at most one is allowed."); } + else if (IsFunctionArgumentSeparator(tempChar)) + { + break; + } else if (!IsAsciiDigit(tempChar) && tempChar != '.') { break; // end of numeric token @@ -88,32 +115,70 @@ namespace MathEngine.Tokenizer return new Token(numberSpan.ToString(), TokenType.Numeric); } - - static private Token GetOperatorToken(char curChar) + /// + /// Parses the expression to read a GenericIdentifier Token + /// + /// The expression being parsed + /// The current index the parser is at + /// A representing a GenericIdentifier for further processing + private static Token ReadIdentifierToken(ReadOnlySpan expression, ref Int32 currentIndex) { - return curChar switch + int start = currentIndex; + while (currentIndex + 1 < expression.Length && IsAsciiLetter(expression[currentIndex + 1])) + { + currentIndex++; + } + ReadOnlySpan identifier = expression.Slice(start, currentIndex - start + 1); + return new Token(identifier.ToString(), TokenType.GenericIdentifier, 0); + } + + + /// + /// Returns the correct Operator Token given a ASCII Character + /// + /// The character to get the correct Operator Token for + /// A which is represented by + /// Thrown when is not a recognized character for an Operator Token + static private Token GetOperatorToken(char c) + { + return c switch { '+' => Token.Plus, '-' => Token.Minus, '*' => Token.Multiply, '/' => Token.Divide, '^' => Token.Exponentiation, - _ => throw new TokenizerException($"Recieved unknown character '{curChar}' when attempting to parse an operator.") + _ => throw new TokenizerException($"Recieved unknown character '{c}' when attempting to parse an operator.") }; } - static private Token GetParenthesisToken(char curChar) + /// + /// Returns the correct Parenthesis Token given a ASCII Character + /// + /// The character to get the correct Parenthesis Token for + /// The correct Parenthesis which is represented by + /// Thrown when is not a recognized character for a Parenthesis + static private Token GetParenthesisToken(char c) { - return curChar switch + return c switch { '(' => Token.LeftParenthesis, ')' => Token.RightParenthesis, - _ => throw new TokenizerException($"Recieved unknown character '{curChar}' when attempting to parse a parenthesis."), + _ => throw new TokenizerException($"Recieved unknown character '{c}' when attempting to parse a parenthesis."), }; } + /// + /// Returns the Function Argument Separator Token + /// + /// The Function Argument Separator + static private Token GetFunctionArgumentSeparatorToken() + { + return Token.FunctionArgumentSeparator; + } + /// /// Tokenizes a given Mathematical expression given as a string to a list of tokens /// @@ -151,6 +216,12 @@ namespace MathEngine.Tokenizer continue; } + if (IsFunctionArgumentSeparator(curChar)) + { + Tokenstack.Add(GetFunctionArgumentSeparatorToken()); + continue; + } + //Number, two cases two consider, case 1) number is something like 142.2; case 2 .5 which is clearly 0.5. //Case 1 if (IsAsciiDigit(curChar)) @@ -162,7 +233,16 @@ namespace MathEngine.Tokenizer { Tokenstack.Add(ReadNumericToken(expression, true, ref i)); continue; - } + } + + // read any iddentifiers + // TODO: Consider how to handle imaginary unit (i). Seems like a parser issue, non-issue in symbolic + if (IsAsciiLetter(curChar)) + { + Tokenstack.Add(ReadIdentifierToken(expression, ref i)); + continue; + + } } //return the stack after triming Tokenstack.TrimExcess(); diff --git a/MathEngine/MathEngine/Types/DecimalValue.cs b/MathEngine/MathEngine/Types/DecimalValue.cs new file mode 100644 index 0000000..3938753 --- /dev/null +++ b/MathEngine/MathEngine/Types/DecimalValue.cs @@ -0,0 +1,373 @@ +using MathEngine.Types.Interfaces; +using System.Diagnostics; + +namespace MathEngine.Types +{ + internal struct DecimalValue : INumericallyOrderable, ITrigonometric + { + + private decimal _value; + + private static readonly DecimalValue _zero = new(0); + + /// + /// The mathematical constant Pi + /// + private static readonly decimal PI = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068m; + private static readonly decimal TWOPI = 6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234136m; + private static readonly decimal HALFPI = 1.570796326794896619231321691639751442098584699687552910487472296153908203143104499314017412671058534m; + + // TODO: Find a set of coefficients where more values can fit into decimal while + // keeping the accuracy just as high + + /// + /// Pre-computed Chebyshev coefficients for a degree 30 Sine approximation + /// + private static readonly decimal[] _sin_cfs = { + 1.107762718434877094787128e-31m, + -1.823732266493779103076113e-35m, + -9.181673072797976602053211e-29m, + 3.225262111842922385008455e-34m, + 6.446939827037235822437697e-26m, + -2.576736665059962220360842e-33m, + -3.868170134617741732466926e-23m, + 1.228109964113387911565451e-32m, + 1.957294106252405728115831e-20m, + -3.882226497276181192594589e-32m, + -8.220635246622832053855188e-18m, + 8.555581455036025700953568e-32m, + 2.811457254345518890772695e-15m, + -1.344198394902903252583665e-31m, + -7.647163731819816458999534e-13m, + 1.512690085816523728430496e-31m, + 1.605904383682161459928373e-10m, + -1.207137865111693794326432e-31m, + -2.505210838544171877505162e-8m, + 6.658710916160807720823915e-32m, + 0.000002755731922398589065255732m, + -2.425218754430548182421541e-32m, + -0.0001984126984126984126984127m, + 5.399220187127378949775714e-33m, + 0.008333333333333333333333333m, + -6.429050541691068943420765e-34m, + -0.1666666666666666666666667m, + 3.144305445341164228755108e-35m, + 1.0m, + -2.646821446601517372109605e-37m}; + + + /// + /// Pre-computed Chebyshev coefficients for a degree 30 Cosine approximation + /// + private static readonly decimal[] _cos_cfs = { + -1.819373578408273903259426e-35m, + 3.210857395660629127502647e-30m, + 3.558623874711111508186483e-34m, + -2.479023574256452751736573e-27m, + -3.120601939944795774454124e-33m, + 1.611734742261012615961468e-24m, + 1.61966781283449908063686e-32m, + -8.896791299969882965587422e-22m, + -5.531921460818070226357292e-32m, + 4.11031762310156955385159e-19m, + 1.307520378404943821183129e-31m, + -1.561920696858280193101514e-16m, + -2.189425802841482649852114e-31m, + 4.779477332387381286306434e-14m, + 2.61434386212678154378457e-31m, + -1.147074559772972468014089e-11m, + -2.210663449693968868683802e-31m, + 2.087675698786809897901e-9m, + 1.296861671422669049472492e-31m, + -0.0000002755731922398589065255651m, + -5.084534256113915594062863e-32m, + 0.00002480158730158730158730159m, + 1.251597093832519719838309e-32m, + -0.001388888888888888888888889m, + -1.738090061385202180342415e-33m, + 0.04166666666666666666666667m, + 1.113240260965897824377927e-34m, + -0.5m, + -2.097353881291299259797553e-36m, + 1.0m + }; + + + public Type UnderlyingType => throw new NotImplementedException(); + + public DecimalValue Value => throw new NotImplementedException(); + + public DecimalValue(decimal value) + { + _value = value; + } + + public DecimalValue(string value) + { + _value = decimal.Parse(value); + } + + + /// + /// Performs radian angle range reduction into the range -Pi/2 <= x <= Pi/2 + /// + /// The radian angle to reduce + /// The radian angle within the range -Pi/2 <= x <= Pi/2 + private static decimal RangeReduction(decimal x) + { + decimal r = x % TWOPI; + + if (r > PI) r -= TWOPI; + else if (r < -PI) r += TWOPI; + + if (r > HALFPI) r = PI - r; + else if (r < -HALFPI) r = -PI - r; + + return r; + } + + /// + /// Evaluates a Polynommial with coefficients at a point using Horners method + /// + /// The coefficients of the polynomial with the leading coefficient first + /// The point to evaluate the polynomial at + /// The evaluation of the polynomial given by at the point + private static decimal HornerPolyEval(ReadOnlySpan cfs, decimal x) + { + decimal result = cfs[0]; // Start with the leading coefficient + for (int i = 1; i < cfs.Length; i++) + { + result = result * x + cfs[i]; + } + return result; + } + + /// + /// Adds two . + /// + /// The first + /// The second + /// The sum of and + public static DecimalValue operator +(DecimalValue a, DecimalValue b) + { + return new DecimalValue(a._value + b._value); + } + + /// + /// Subtracts two . + /// + /// The first + /// The second + /// The difference of and + public static DecimalValue operator -(DecimalValue a, DecimalValue b) + { + return new DecimalValue(a._value - b._value); + } + + /// + /// Multiplies two . + /// + /// The first + /// The second + /// The product of and + public static DecimalValue operator *(DecimalValue a, DecimalValue b) + { + return new DecimalValue(a._value * b._value); + } + + /// + /// Divides two . + /// + /// The first + /// The second + /// The quotient of and + /// Thrown when is equal to zero + public static DecimalValue operator /(DecimalValue a, DecimalValue b) + { + if (b._value == 0) + { + throw new DivideByZeroException("DIVISION BY ZERO!"); + } + return new DecimalValue(a._value / b._value); + } + + /// + /// Compares two for equality + /// + /// The first + /// The second + /// True if and are equal, False otherwise + public static bool operator ==(DecimalValue a, DecimalValue b) + { + return a._value == b._value; + } + + /// + /// Compares two for inequality + /// + /// The first + /// The second + /// True if and are not equal, False otherwise + public static bool operator !=(DecimalValue a, DecimalValue b) + { + return a._value != b._value; + } + + /// + /// Compares the current instance to another + /// + /// The instance to compare to the current instance + /// A signed number indicating the relative values of this instance and value. + /// + /// + /// Return value + /// Meaning + /// + /// + /// Less than zero + /// This instance is less than . + /// + /// + /// Zero + /// This instance is equal to . + /// + /// + /// Greater than zero + /// This instance is greater than . + /// + /// + /// + public int CompareTo(DecimalValue other) + { + return _value.CompareTo(other._value); + } + + /// + /// Compares if the current instance is less than another instance. + /// + /// instance to compare the current instanct to. + /// True if the current instance is less thatn , False otherwise + public bool LessThan(DecimalValue other) + { + return _value < other._value; + } + + /// + /// Compares if the current instance is less than or equal to another instance. + /// + /// instance to compare the current instanct to. + /// True if the current instance is less thatn , False otherwise + public bool LessThanOrEqual(DecimalValue other) + { + return _value <= other._value; + } + + /// + /// Compares if the current instance is greater than another instance. + /// + /// instance to compare the current instanct to. + /// True if the current instance is greater thatn , False otherwise + public bool GreaterThan(DecimalValue other) + { + return _value > other._value; + } + + /// + /// Compares if the current instance is greater than or equal to another instance. + /// + /// instance to compare the current instanct to. + /// True if the current instance is greater thatn , False otherwise + public bool GreaterThanOrEqual(DecimalValue other) + { + return _value >= other._value; + } + + /// + /// Adds the current instance to + /// + /// + /// + public DecimalValue Add(DecimalValue other) + { + return this + other; + } + + public DecimalValue Subtract(DecimalValue other) + { + return this - other; + } + + public DecimalValue Multiply(DecimalValue other) + { + return this * other; + } + + public DecimalValue Divide(DecimalValue other) + { + return this / other; + } + + public DecimalValue Negate() + { + return new DecimalValue(decimal.Negate(_value)); + } + + public bool Equal(DecimalValue other) + { + return _value == other._value; + } + + public bool NotEqual(DecimalValue other) + { + return !Equal(other); + } + + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + public override bool Equals(object? obj) + { + if (!(obj is DecimalValue)) + return false; + + DecimalValue mys = (DecimalValue)obj; + return mys._value == _value; + } + + public override string ToString() + { + return "DecimalValue: " + _value.ToString(); + } + + public DecimalValue Sin() + { + decimal reduced_x = DecimalValue.RangeReduction(_value); + return new DecimalValue(HornerPolyEval(DecimalValue._sin_cfs, reduced_x)); + } + + public DecimalValue Cos() + { + decimal reduced_x = DecimalValue.RangeReduction(_value); + return new DecimalValue(HornerPolyEval(DecimalValue._cos_cfs, reduced_x)); + } + + public DecimalValue Tan() + { + DecimalValue reduced_x = new DecimalValue(DecimalValue.RangeReduction(_value)); + return reduced_x.Sin() / reduced_x.Cos(); + + } + + public DecimalValue Max(DecimalValue other) + { + return this.GreaterThanOrEqual(other) ? this : other; + } + + public DecimalValue Min(DecimalValue other) + { + return this.LessThanOrEqual(other) ? this : other; + } + } +} diff --git a/MathEngine/MathEngine/Types/Interfaces/INumeric.cs b/MathEngine/MathEngine/Types/Interfaces/INumeric.cs new file mode 100644 index 0000000..3ebc165 --- /dev/null +++ b/MathEngine/MathEngine/Types/Interfaces/INumeric.cs @@ -0,0 +1,79 @@ +using System.Numerics; + +namespace MathEngine.Types.Interfaces +{ + /// + /// Base Interface that defines numerical values + /// + internal interface INumeric where T : struct, INumeric + { + T Value { get; } + + /// + /// Adds this numeric value to another + /// + /// The numeric value to add + /// + /// A new instance representing the sum of this and + /// + T Add(T other); + + /// + /// Subtracts another numeric value from this one + /// + /// The numeric value to subtract + /// + /// A new instance representing the result of the subtraction + /// + T Subtract(T other); + + /// + /// Multiplies this numeric value by another + /// + /// The numeric value to multiply by + /// + /// A new instance representing the product of this and + /// + T Multiply(T other); + + /// + /// Divides this numeric value by another + /// + /// The numeric value to divide by + /// + /// A new instance representing the quotient of this and + /// + T Divide(T other); + + /// + /// Returns the negation of this numeric value + /// + /// + /// A new instance representing the negated value + /// + T Negate(); + + /// + /// Determines whether this numeric value is equal to the specified other value. + /// + /// The other numeric value to compare. + /// + /// true if this value is equal to ; otherwise, false. + /// + bool Equal(T other); + + /// + /// Determines whether this numeric value is not equal to the specified other value. + /// + /// The other numeric value to compare. + /// + /// true if this value is not equal to ; otherwise, false. + /// + bool NotEqual(T other); + + /// + /// Returns the of the instance + /// + Type UnderlyingType { get; } + } +} diff --git a/MathEngine/MathEngine/Types/Interfaces/INumericNode.cs b/MathEngine/MathEngine/Types/Interfaces/INumericNode.cs new file mode 100644 index 0000000..86cdcc1 --- /dev/null +++ b/MathEngine/MathEngine/Types/Interfaces/INumericNode.cs @@ -0,0 +1,9 @@ +namespace MathEngine.Types.Interfaces +{ + internal interface INumericNode + { + Type NumericType { get; } + + void CopyTo(ref T destination) where T : struct; + } +} diff --git a/MathEngine/MathEngine/Types/Interfaces/INumericallyOrderable.cs b/MathEngine/MathEngine/Types/Interfaces/INumericallyOrderable.cs new file mode 100644 index 0000000..38c5149 --- /dev/null +++ b/MathEngine/MathEngine/Types/Interfaces/INumericallyOrderable.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MathEngine.Types.Interfaces +{ + /// + /// Extension interface that represents a numeric value that supports ordering operations. + /// + internal interface INumericallyOrderable : INumeric where T : struct, INumericallyOrderable + { + /// + /// Compares this numeric value with another. + /// + /// The other comparable numeric value. + /// + /// Less than zero if this < other, 0 if equal, greater than zero if this > other. + /// + int CompareTo(T other); + + + /// + /// Determines if this numeric value is less than the specified other value. + /// + /// The other numeric value to compare. + /// True if this < other, False otherwise + bool LessThan(T other); + + + /// + /// Determines whether this value is less than or equal to the specified other value. + /// + /// The other numeric value to compare. + /// True if this <= other, False otherwise + bool LessThanOrEqual(T other); + + /// + /// Determines whether this value is greater than the specified other value. + /// + /// The other numeric value to compare. + /// True if this > other, False otherwise + bool GreaterThan(T other); + + + /// + /// Determines whether this value is less than or equal to the specified other value. + /// + /// The other numeric value to compare. + /// True if this >= other, False otherwise + bool GreaterThanOrEqual(T other); + + /// + /// Returns the maximum of this node and another node. + /// + /// The other numeric node. + /// A new node representing the maximum value. + T Max(T other); + + /// + /// Returns the minimum of this node and another node. + /// + /// The other numeric node. + /// A new node representing the minimum value. + T Min(T other); + } + + /// + /// AST-level interface representing a numeric node that supports ordering operations. + /// + internal interface INumericallyOrderable : INumericNode + { + /// + /// Compares this node's value with another node. + /// + /// The other numeric node to compare. + /// + /// Less than zero if this < other, 0 if equal, greater than zero if this > other. + /// + int CompareTo(INumericNode other); + + /// + /// Determines if this node's value is less than the other node's value. + /// + /// The other numeric node to compare. + /// True if this < other, False otherwise + bool LessThan(INumericNode other); + + /// + /// Determines if this node's value is less than or equal to the other node's value. + /// + /// The other numeric node to compare. + /// True if this <= other, False otherwise + bool LessThanOrEqual(INumericNode other); + + /// + /// Determines if this node's value is greater than the other node's value. + /// + /// The other numeric node to compare. + /// True if this > other, False otherwise + bool GreaterThan(INumericNode other); + + /// + /// Determines if this node's value is greater than or equal to the other node's value. + /// + /// The other numeric node to compare. + /// True if this >= other, False otherwise + bool GreaterThanOrEqual(INumericNode other); + + /// + /// Returns the maximum of this node and another node. + /// + /// The other numeric node. + /// A new node representing the maximum value. + INumericallyOrderable Max(INumericNode other); + + /// + /// Returns the minimum of this node and another node. + /// + /// The other numeric node. + /// A new node representing the minimum value. + INumericallyOrderable Min(INumericNode other); + } +} diff --git a/MathEngine/MathEngine/Types/Interfaces/ITrigonometric.cs b/MathEngine/MathEngine/Types/Interfaces/ITrigonometric.cs new file mode 100644 index 0000000..b9362aa --- /dev/null +++ b/MathEngine/MathEngine/Types/Interfaces/ITrigonometric.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MathEngine.Types.Interfaces +{ + /// + /// Generic Interface that defines support for Trigonometric evaluation + /// + /// + internal interface ITrigonometric where T : struct, INumeric + { + /// + /// Returns the sine of this value + /// + T Sin(); + + /// + /// Returns the cosine of this value + /// + T Cos(); + + /// + /// Returns the tangent of this value + /// + T Tan(); + } + + + internal interface ITrigonometricNode: INumericNode + { + /// + /// Returns the sine of this value + /// + INumericNode Sin(); + + /// + /// Returns the cosine of this value + /// + INumericNode Cos(); + + /// + /// Returns the tangent of this value + /// + INumericNode Tan(); + } +} diff --git a/MathEngine/MathRunner/Program.cs b/MathEngine/MathRunner/Program.cs index 6f010bb..d8fb712 100644 --- a/MathEngine/MathRunner/Program.cs +++ b/MathEngine/MathRunner/Program.cs @@ -4,11 +4,136 @@ namespace MathRunner { internal class Program { + + static readonly decimal[] SIN_COEFF = {-2.495353579956845068391614e-22m, +5.577877028144835944149859e-21m, +-5.907037282921367151874229e-20m, +3.942306394997070814280735e-19m, +-1.860603293633641604845514e-18m, +6.605428876334905572700573e-18m, +-1.83190469182408107133773e-17m, +4.06860589192645688022598e-17m, +-7.358359464590914836426366e-17m, +1.097329189752797643166105e-16m, +-1.441182766313743229879074e-16m, +1.40511452613827650863302e-16m, +2.689837823485177032543228e-15m, +8.817888613969593259744191e-17m, +-7.647698614509161378062996e-13m, +2.706140963906206196147425e-17m, +1.605904270053538957745356e-10m, +3.932205925254663059528159e-18m, +-2.505210838655286048763183e-8m, +2.533366734800068550746431e-19m, +0.000002755731922398543165479237m, +6.481293982930866844259318e-21m, +-0.0001984126984126984133938607m, +5.479548578024028585883317e-23m, +0.008333333333333333333330317m, +1.071395263377213212738481e-25m, +-0.1666666666666666666666667m, +1.61588801274554481697818e-29m, +1.0m, +-3.751019264464499084009736e-34m}; + + static readonly decimal[] s2 = {1.107762718434877094787128e-31m, +-1.823732266493779103076113e-35m, +-9.181673072797976602053211e-29m, +3.225262111842922385008455e-34m, +6.446939827037235822437697e-26m, +-2.576736665059962220360842e-33m, +-3.868170134617741732466926e-23m, +1.228109964113387911565451e-32m, +1.957294106252405728115831e-20m, +-3.882226497276181192594589e-32m, +-8.220635246622832053855188e-18m, +8.555581455036025700953568e-32m, +2.811457254345518890772695e-15m, +-1.344198394902903252583665e-31m, +-7.647163731819816458999534e-13m, +1.512690085816523728430496e-31m, +1.605904383682161459928373e-10m, +-1.207137865111693794326432e-31m, +-2.505210838544171877505162e-8m, +6.658710916160807720823915e-32m, +0.000002755731922398589065255732m, +-2.425218754430548182421541e-32m, +-0.0001984126984126984126984127m, +5.399220187127378949775714e-33m, +0.008333333333333333333333333m, +-6.429050541691068943420765e-34m, +-0.1666666666666666666666667m, +3.144305445341164228755108e-35m, +1.0m, +-2.646821446601517372109605e-37m}; + + + static readonly decimal[] s3 = { 9.916326246264954201615594e-30m, +1.54319928865030320494218e-29m, +-2.674184926393101367468514e-28m, +-2.692817072692041357738017e-28m, +6.587612243251582675930865e-26m, +2.093202438542293840804485e-27m, +-3.868835054593715102015364e-23m, +-9.553041629841611461032692e-27m, +1.95729616572122280801307e-20m, +2.839955456858767175704141e-26m, +-8.220635290513026145246676e-18m, +-5.767530270581598294967383e-26m, +2.811457254411262970766708e-15m, +8.161593358681450120770325e-26m, +-7.647163731820510961992906e-13m, +-8.059975709448137639642111e-26m, +1.605904383682161971192382e-10m, +5.477723421466464288236971e-26m, +-2.50521083854417188005417e-8m, +-2.485058233812166433395256e-26m, +0.00000275573192239858906526391m, +7.148546378130704537447194e-27m, +-0.0001984126984126984126984142m, +-1.202593758014493393872389e-27m, +0.008333333333333333333333333m, +1.046429449740262240020432e-28m, +-0.1666666666666666666666667m, +-3.934554842474513984886404e-30m, +1.0m, +3.934032899734993769175678e-32m,}; + public static decimal Sin(decimal x) + { + decimal result = SIN_COEFF[0]; // Start with the leading coefficient + for (int i = 1; i < SIN_COEFF.Length; i++) + { + result = result * x + SIN_COEFF[i]; + } + return result; + } + + public static decimal Sin2(decimal x) + { + decimal result = s2[0]; // Start with the leading coefficient + for (int i = 1; i < s2.Length; i++) + { + result = result * x + s2[i]; + } + return result; + } + + public static decimal Sin3(decimal x) + { + decimal result = s3[0]; // Start with the leading coefficient + for (int i = 1; i < s3.Length; i++) + { + result = result * x + s3[i]; + } + return result; + } + static void Main(string[] args) { Console.WriteLine("Evaluting expression..."); - Expression exp = new("54+2+1"); - Console.WriteLine(exp.Evaluate()); + // Expression exp = new("sin(3.14159265)+1+max(1,2,3,4,5,6)"); + Expression exp = new("sin(3.14159265)*sin(3.14159265)+cos(3.14159265)*cos(3.14159265) + tan(1.4)"); + Console.WriteLine(exp.Evaluate().ToString()); Console.WriteLine("Hello, World!"); } }