diff --git a/README.md b/README.md index 1b21b47..b77cac6 100644 --- a/README.md +++ b/README.md @@ -136,14 +136,14 @@ decimal angle = interpreter.Interpret("degrees(pi / 2)"); The standard library includes: -- Base functions: `abs`, `sign`, `min`, `max` +- Base functions: `abs`, `sign`, `min`, `max`, `sqrt`, `pow`, `log`, `ln`, `lerp`, `smoothstep`, `map`, `normalize` - Angle helpers: `degrees`, `radians`, `wrapangle` - Rounding helpers: `floor`, `ceiling`, `truncate`, `frac`, `round`, `clamp` - Trigonometry: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2` - Hyperbolic trigonometry: `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh` -- Number theory: `fac`, `ncr`, `npr`, `gcd`, `lcm` +- Number theory: `fac`, `ncr`, `npr`, `gcd`, `lcm`, `fib`, `iseven`, `isodd`, `isint`, `isprime` - Random helpers: `flip`, `bern`, `rand`, `rands`, `randn`, `randns` -- Constants: `pi`, `e`, `tau`, `phi`, `goldenratio`, `eurler`, `omega` +- Constants: `pi`, `e`, `tau`, `phi`, `goldenratio`, `eurler`, `euler`, `omega` ## Custom Functions diff --git a/src/StandardLibrary/Functions/Lerp.cs b/src/StandardLibrary/Functions/Lerp.cs index 44ce930..029f9a1 100644 --- a/src/StandardLibrary/Functions/Lerp.cs +++ b/src/StandardLibrary/Functions/Lerp.cs @@ -56,9 +56,9 @@ namespace CSMic.StandardLibrary.Functions var input2 = _args[1].Value; var input3 = _args[2].Value; - decimal start = Convert.ToDecimal(input); - decimal end = Convert.ToDecimal(input2); - decimal ammount = Convert.ToDecimal(input3); + decimal start = Convert.ToDecimal(input.Value); + decimal end = Convert.ToDecimal(input2.Value); + decimal ammount = Convert.ToDecimal(input3.Value); if (start == end) { diff --git a/src/StandardLibrary/Functions/Map.cs b/src/StandardLibrary/Functions/Map.cs index 23ecccd..fbb6811 100644 --- a/src/StandardLibrary/Functions/Map.cs +++ b/src/StandardLibrary/Functions/Map.cs @@ -64,11 +64,11 @@ namespace CSMic.StandardLibrary.Functions var input4 = _args[3].Value; var input5 = _args[4].Value; - decimal number = Convert.ToDecimal(input); - decimal oldMinimum = Convert.ToDecimal(input2); - decimal oldMaximum = Convert.ToDecimal(input3); - decimal newMinimum = Convert.ToDecimal(input4); - decimal newMaximum = Convert.ToDecimal(input5); + decimal number = Convert.ToDecimal(input.Value); + decimal oldMinimum = Convert.ToDecimal(input2.Value); + decimal oldMaximum = Convert.ToDecimal(input3.Value); + decimal newMinimum = Convert.ToDecimal(input4.Value); + decimal newMaximum = Convert.ToDecimal(input5.Value); if (oldMinimum == oldMaximum) { diff --git a/src/StandardLibrary/Functions/Normalize.cs b/src/StandardLibrary/Functions/Normalize.cs index 2a461d1..070914d 100644 --- a/src/StandardLibrary/Functions/Normalize.cs +++ b/src/StandardLibrary/Functions/Normalize.cs @@ -57,9 +57,9 @@ namespace CSMic.StandardLibrary.Functions var input2 = _args[1].Value; var input3 = _args[2].Value; - decimal number = Convert.ToDecimal(input); - decimal minimum = Convert.ToDecimal(input2); - decimal maximum = Convert.ToDecimal(input3); + decimal number = Convert.ToDecimal(input.Value); + decimal minimum = Convert.ToDecimal(input2.Value); + decimal maximum = Convert.ToDecimal(input3.Value); if (minimum == maximum) { diff --git a/src/StandardLibrary/Functions/NumberTheory/Fibonacci.cs b/src/StandardLibrary/Functions/NumberTheory/Fibonacci.cs index f330105..d4f881b 100644 --- a/src/StandardLibrary/Functions/NumberTheory/Fibonacci.cs +++ b/src/StandardLibrary/Functions/NumberTheory/Fibonacci.cs @@ -86,7 +86,7 @@ namespace CSMic.StandardLibrary.Functions.NumberTheory { var input = _args[0].Value; - int index = Convert.ToInt32(input); + int index = Convert.ToInt32(input.Value); if(index <= 0 || index > 140) { diff --git a/src/StandardLibrary/Functions/NumberTheory/Identity.cs b/src/StandardLibrary/Functions/NumberTheory/Identity.cs index a743224..d0350da 100644 --- a/src/StandardLibrary/Functions/NumberTheory/Identity.cs +++ b/src/StandardLibrary/Functions/NumberTheory/Identity.cs @@ -53,7 +53,7 @@ namespace CSMic.StandardLibrary.Functions.NumberTheory { var input = _args[0].Value; - decimal value = Convert.ToDecimal(input); + decimal value = Convert.ToDecimal(input.Value); return new FunctionValue(FunctionValueType.Numeric, IsEven.CalculateIsEven(value) ? 1m : 0m); }); @@ -61,7 +61,7 @@ namespace CSMic.StandardLibrary.Functions.NumberTheory internal static bool CalculateIsEven(decimal value) { - return true; + return IsInt.CalculateIsInt(value) && value % 2m == 0m; } } @@ -113,11 +113,16 @@ namespace CSMic.StandardLibrary.Functions.NumberTheory { var input = _args[0].Value; - decimal value = Convert.ToDecimal(input); + decimal value = Convert.ToDecimal(input.Value); - return new FunctionValue(FunctionValueType.Numeric, IsEven.CalculateIsEven(value) ? 0m : 1m); + return new FunctionValue(FunctionValueType.Numeric, CalculateIsOdd(value) ? 1m : 0m); }); } + + internal static bool CalculateIsOdd(decimal value) + { + return IsInt.CalculateIsInt(value) && value % 2m != 0m; + } } /// @@ -169,7 +174,7 @@ namespace CSMic.StandardLibrary.Functions.NumberTheory { var input = _args[0].Value; - decimal value = Convert.ToDecimal(input); + decimal value = Convert.ToDecimal(input.Value); return new FunctionValue(FunctionValueType.Numeric, CalculateIsInt(value) ? 1m : 0m); }); @@ -255,7 +260,7 @@ namespace CSMic.StandardLibrary.Functions.NumberTheory { var input = _args[0].Value; - decimal value = Convert.ToDecimal(input); + decimal value = Convert.ToDecimal(input.Value); return new FunctionValue(FunctionValueType.Numeric, CalculateIsPrime(value) ? 1m : 0m); }); diff --git a/src/StandardLibrary/Functions/SmoothStep.cs b/src/StandardLibrary/Functions/SmoothStep.cs index fe9fd40..5517279 100644 --- a/src/StandardLibrary/Functions/SmoothStep.cs +++ b/src/StandardLibrary/Functions/SmoothStep.cs @@ -5,7 +5,7 @@ using System.Text; namespace CSMic.StandardLibrary.Functions { /// - /// Represents the standard-library lerp smooth-step function. + /// Represents the standard-library smoothstep function. /// /// /// This function evaluates a value between two edges, clamps it to the range from 0 through 1, and @@ -16,17 +16,17 @@ namespace CSMic.StandardLibrary.Functions /// /// Gets the expression-language name used to invoke this function. /// - /// lerp. + /// smoothstep. public string Name { get { - return "lerp"; + return "smoothstep"; } } /// - /// Gets the argument signature expected by the lerp smooth-step function. + /// Gets the argument signature expected by the smoothstep function. /// /// Three numeric arguments named startEdge, endEdge, and value. public override IEnumerable ExpectedArguments @@ -40,7 +40,7 @@ namespace CSMic.StandardLibrary.Functions } /// - /// Executes the lerp smooth-step function. + /// Executes the smoothstep function. /// /// /// The evaluated arguments supplied to the function. Exactly three numeric arguments are expected. @@ -56,9 +56,9 @@ namespace CSMic.StandardLibrary.Functions var input2 = _args[1].Value; var input3 = _args[2].Value; - decimal startEdge = Convert.ToDecimal(input); - decimal endEdge = Convert.ToDecimal(input2); - decimal value = Convert.ToDecimal(input3); + decimal startEdge = Convert.ToDecimal(input.Value); + decimal endEdge = Convert.ToDecimal(input2.Value); + decimal value = Convert.ToDecimal(input3.Value); var normalization = Math.Clamp((value - startEdge) / (endEdge - startEdge), 0, 1); var polynomialization = normalization * normalization * (3 - (2 * normalization)); diff --git a/src/StandardLibrary/Initializer.cs b/src/StandardLibrary/Initializer.cs index 5cc5970..8b0ae31 100644 --- a/src/StandardLibrary/Initializer.cs +++ b/src/StandardLibrary/Initializer.cs @@ -65,6 +65,13 @@ namespace CSMic.StandardLibrary inputInterpreter.RegisterFunction(new Min()); inputInterpreter.RegisterFunction(new Max()); inputInterpreter.RegisterFunction(new SquareRoot()); + inputInterpreter.RegisterFunction(new Power()); + inputInterpreter.RegisterFunction(new Log()); + inputInterpreter.RegisterFunction(new Natural_Log()); + inputInterpreter.RegisterFunction(new Lerp()); + inputInterpreter.RegisterFunction(new SmoothStep()); + inputInterpreter.RegisterFunction(new Map()); + inputInterpreter.RegisterFunction(new Normalize()); } /// Initializes the angle-related functions. @@ -97,6 +104,11 @@ namespace CSMic.StandardLibrary inputInterpreter.RegisterFunction(new Permutations()); inputInterpreter.RegisterFunction(new GreatestCommonDivisor()); inputInterpreter.RegisterFunction(new LeastCommonMultiple()); + inputInterpreter.RegisterFunction(new Fibonacci()); + inputInterpreter.RegisterFunction(new IsEven()); + inputInterpreter.RegisterFunction(new IsOdd()); + inputInterpreter.RegisterFunction(new IsInt()); + inputInterpreter.RegisterFunction(new IsPrime()); } /// Initializes the rounding functions. @@ -178,6 +190,7 @@ namespace CSMic.StandardLibrary inputInterpreter.Interpret("tau :: 6.2831853071795862"); inputInterpreter.Interpret("phi :: 1.6180339887498948"); inputInterpreter.Interpret("goldenratio :: 1.6180339887498948"); + inputInterpreter.Interpret("eurler :: 0.5772156649015329"); inputInterpreter.Interpret("euler :: 0.5772156649015329"); inputInterpreter.Interpret("omega :: 0.5671432904097839"); } diff --git a/src/StandardLibrary/README.md b/src/StandardLibrary/README.md index fed92ea..de1a4c4 100644 --- a/src/StandardLibrary/README.md +++ b/src/StandardLibrary/README.md @@ -42,6 +42,7 @@ decimal result = interpreter.Interpret("max(10, abs(-12))"); - `phi` - `goldenratio` - `eurler` +- `euler` - `omega` Constants are stored as interpreter variables, so they can be used in normal expressions: @@ -59,6 +60,14 @@ Base functions: - `sign(value)` - `min(left, right)` - `max(left, right)` +- `sqrt(value)` +- `pow(base, exponent)` +- `log(value, base)` +- `ln(value)` +- `lerp(start, end, amount)` +- `smoothstep(startEdge, endEdge, value)` +- `map(value, oldMinimum, oldMaximum, newMinimum, newMaximum)` +- `normalize(value, minimum, maximum)` Angle functions: @@ -101,6 +110,11 @@ Number theory functions: - `npr(n, r)` - `gcd(left, right)` - `lcm(left, right)` +- `fib(index)` +- `iseven(value)` +- `isodd(value)` +- `isint(value)` +- `isprime(value)` Random functions: diff --git a/src/Tests/NumberTheoryFunctionsTests.cs b/src/Tests/NumberTheoryFunctionsTests.cs index dda332f..272ba7d 100644 --- a/src/Tests/NumberTheoryFunctionsTests.cs +++ b/src/Tests/NumberTheoryFunctionsTests.cs @@ -80,5 +80,35 @@ public class NumberTheoryFunctionsTests // Gamma(1.5) ~= sqrt(pi)/2 ~= 0.8862269254527579 AssertApprox(res, 0.8862269254527579m, 0.0000000000001m, _interp); } -} + [Test] + public void Fibonacci_Works_And_Validates() + { + AssertSuccess(_interp.Interpret("fib(1)"), 1m, _interp); + AssertSuccess(_interp.Interpret("fib(2)"), 1m, _interp); + AssertSuccess(_interp.Interpret("fib(10)"), 55m, _interp); + AssertSuccess(_interp.Interpret("fib(0)"), 0m, _interp); + AssertSuccess(_interp.Interpret("fib(-1)"), 0m, _interp); + } + + [Test] + public void IdentityFunctions_Work() + { + AssertSuccess(_interp.Interpret("iseven(2)"), 1m, _interp); + AssertSuccess(_interp.Interpret("iseven(3)"), 0m, _interp); + AssertSuccess(_interp.Interpret("iseven(2.5)"), 0m, _interp); + + AssertSuccess(_interp.Interpret("isodd(3)"), 1m, _interp); + AssertSuccess(_interp.Interpret("isodd(2)"), 0m, _interp); + AssertSuccess(_interp.Interpret("isodd(2.5)"), 0m, _interp); + + AssertSuccess(_interp.Interpret("isint(2)"), 1m, _interp); + AssertSuccess(_interp.Interpret("isint(2.5)"), 0m, _interp); + + AssertSuccess(_interp.Interpret("isprime(2)"), 1m, _interp); + AssertSuccess(_interp.Interpret("isprime(97)"), 1m, _interp); + AssertSuccess(_interp.Interpret("isprime(1)"), 0m, _interp); + AssertSuccess(_interp.Interpret("isprime(9)"), 0m, _interp); + AssertSuccess(_interp.Interpret("isprime(2.5)"), 0m, _interp); + } +} diff --git a/src/Tests/StdlibFunctionsTests.cs b/src/Tests/StdlibFunctionsTests.cs index f921f68..5392a0d 100644 --- a/src/Tests/StdlibFunctionsTests.cs +++ b/src/Tests/StdlibFunctionsTests.cs @@ -26,6 +26,12 @@ public class StdlibFunctionsTests Assert.That(interp.StringValue, Is.EqualTo(string.Empty)); } + private static void AssertApprox(decimal result, decimal expected, decimal tol, InputInterpreter interp) + { + Assert.That(Math.Abs(result - expected) <= tol, $"Expected ~{expected} +/- {tol}, got {result}"); + Assert.That(interp.StringValue, Is.EqualTo(string.Empty)); + } + [TestCase("abs(-1)", 1)] [TestCase("abs(0)", 0)] [TestCase("abs(2)", 2)] @@ -98,12 +104,60 @@ public class StdlibFunctionsTests AssertSuccess(result, expected, _interp); } + [Test] + public void Power_Works() + { + AssertSuccess(_interp.Interpret("pow(2, 3)"), 8m, _interp); + AssertApprox(_interp.Interpret("pow(9, 0.5)"), 3m, 0.0000000000001m, _interp); + } + + [Test] + public void Logarithms_Work() + { + AssertSuccess(_interp.Interpret("log(8, 2)"), 3m, _interp); + AssertApprox(_interp.Interpret("ln(e)"), 1m, 0.0000000000001m, _interp); + } + + [Test] + public void Lerp_Works() + { + AssertSuccess(_interp.Interpret("lerp(10, 20, 0)"), 10m, _interp); + AssertSuccess(_interp.Interpret("lerp(10, 20, 0.25)"), 12.5m, _interp); + AssertSuccess(_interp.Interpret("lerp(10, 20, 1)"), 20m, _interp); + AssertSuccess(_interp.Interpret("lerp(10, 10, 0.5)"), 10m, _interp); + } + + [Test] + public void SmoothStep_Works() + { + AssertSuccess(_interp.Interpret("smoothstep(0, 1, -1)"), 0m, _interp); + AssertSuccess(_interp.Interpret("smoothstep(0, 1, 0.5)"), 0.5m, _interp); + AssertSuccess(_interp.Interpret("smoothstep(0, 1, 2)"), 1m, _interp); + } + + [Test] + public void Map_Works_And_HandlesEmptySourceRange() + { + AssertSuccess(_interp.Interpret("map(5, 0, 10, 0, 100)"), 50m, _interp); + AssertSuccess(_interp.Interpret("map(15, 10, 20, 100, 200)"), 150m, _interp); + AssertSuccess(_interp.Interpret("map(5, 1, 1, 0, 100)"), 0m, _interp); + } + + [Test] + public void Normalize_Works_And_HandlesEmptyRange() + { + AssertSuccess(_interp.Interpret("normalize(5, 0, 10)"), 0.5m, _interp); + AssertSuccess(_interp.Interpret("normalize(15, 10, 20)"), 0.5m, _interp); + AssertSuccess(_interp.Interpret("normalize(5, 1, 1)"), 0m, _interp); + } + [TestCase("pi", "3.1415926535897931")] [TestCase("e", "2.7182818284590451")] [TestCase("tau", "6.2831853071795862")] [TestCase("phi", "1.6180339887498948")] [TestCase("goldenratio", "1.6180339887498948")] [TestCase("eurler", "0.5772156649015329")] + [TestCase("euler", "0.5772156649015329")] [TestCase("omega", "0.5671432904097839")] public void Constants_AreAvailable(string expr, string expectedStr) {