diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e3f02e093..235bda117 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -323,6 +323,17 @@ negative even integer or off the integers, since the extension through the gamma | `"n!!".ToEntity()` | throws `UnhandledParseException` | `n!!` | | `"n!!!".ToEntity()` | throws `UnhandledParseException` | throws `UnrecognizedFunctionParseException` | +### `mod` compiles over `Complex` + +**Not silent: what was refused is compiled.** `Compile` refused `mod` with `UncompilableNodeException`, +the operator not being defined for `System.Numerics.Complex`. It now takes the remainder `Compile` takes: the floored +one where both sides are real, and `NaN` where either is not +([#2195](https://github.com/asc-community/AngouriMath/issues/2195)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"x mod 3".ToEntity().Compile("x")(7)` | `UncompilableNodeException` | `(1, 0)` | + ### A compiled statement with no truth value has none **Silent in part: a compiled comparison has the truth value evaluation gives it.** A comparison over `Complex` was refused diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs index d855dae6f..5049c301b 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs @@ -216,6 +216,12 @@ public virtual Expression ConvertBinaryNode(Expression left, Expression right, E // the shortest expression that turns the one into the other for every pair of // signs. Not widened to double the way division is -- the remainder of two // integers is an integer, and widening would only lose that. + // Over the complex numbers the remainder FastExpression takes, the floored one where + // both are real and NaN where either is not, as the interpreter leaves it unevaluated: + // the operator is not defined for System.Numerics.Complex, and this was refused. + // https://github.com/asc-community/AngouriMath/issues/2195 + Modf when left.Type == typeof(System.Numerics.Complex) && right.Type == typeof(System.Numerics.Complex) + => Expression.Call(complexRemainder, left, right), Modf => Operator(ExpressionType.Modulo, Operator(ExpressionType.Add, Operator(ExpressionType.Modulo, left, right), right), right), @@ -452,6 +458,9 @@ private static MethodInfo Conv(Expression> e) { (ExpressionType.GreaterThanOrEqual, typeof(BigInteger)), Cmp((a, b) => a >= b) }, }; + [ConstantField] private static readonly MethodInfo complexRemainder = + ((MethodCallExpression)((Expression>)((a, b) => RealOnly.Mod(a, b))).Body).Method; + private static MethodInfo Op(Expression> e) => ((BinaryExpression)e.Body).Method ?? throw new AngouriBugException($"{typeof(T)} defines this operator as an instruction, not a method"); diff --git a/Sources/Tests/UnitTests/Convenience/ModulusTest.cs b/Sources/Tests/UnitTests/Convenience/ModulusTest.cs index cf4a6b23a..c90d67963 100644 --- a/Sources/Tests/UnitTests/Convenience/ModulusTest.cs +++ b/Sources/Tests/UnitTests/Convenience/ModulusTest.cs @@ -190,6 +190,20 @@ public void TheStackMachineRefusesANonRealArgument() => public void TheLinqCompilerAgreesWithTheInterpreter(int at, int expected) => Assert.Equal(expected, "x mod 3".ToEntity().Compile("x")(at)); + /// + /// Over , where the operator is not defined and this was refused, the + /// LINQ compiler takes the remainder the stack machine does. + /// https://github.com/asc-community/AngouriMath/issues/2195 + /// + [Fact] + public void TheLinqCompilerTakesTheStackMachinesRemainderOverTheComplexNumbers() + { + var compiled = "x mod 3".ToEntity().Compile("x"); + Assert.Equal(new Complex(1, 0), compiled(7)); + Assert.Equal(new Complex(2, 0), compiled(-7)); + Assert.True(double.IsNaN(compiled(new Complex(2, 1)).Real)); + } + [Fact] public void TheLinqCompilerKeepsTheFractionalPart() => Assert.Equal(1.5, "x mod y".ToEntity().Compile("x", "y")(7.5, 2), 9);