diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 2212639e4..7a602ae02 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -607,6 +607,24 @@ interpreter leaves them as written ([#2195](https://github.com/asc-community/Ang | `"valuation(x, 2)".ToEntity().Compile("x").Call(12)` | `UncompilableNodeException` | `(2, 0)` | | `"valuation(x, 2)".ToEntity().Compile("x")(0)` | `UncompilableNodeException` | `Infinity` | +### Compiling over the reals can refuse what is not real + +**Not silent, and only where asked for: `MathS.Settings.RefuseNonRealCompilation`, off by default, refuses to compile into a +function returning `double` or `float` an expression that is not real wherever it is defined.** Over the reals every value +computed on the way is real, so `sin(arcsin(x))` at 2, which is 2, is NaN there, as `1/x` is at 0. That is now documented +on every `Compile` overload, and with the setting on, such an expression throws `UncompilableNodeException`, so that a NaN +means only that there is no value. The check is structural and conservative: `sqrt(x)` and `ln(x)` are refused too. + +**Silent: `floor`, `ceil`, `round`, `min` and `max` of real arguments are known to be real**, which the derivative of a +modulus or a sign needs, so these are answered where they were left unevaluated +([#2286](https://github.com/asc-community/AngouriMath/issues/2286)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"x^y".Compile("x", "y")` under the setting | (no setting) | throws `UncompilableNodeException` | +| `"derivative(abs(floor(x)), x)".ToEntity().Simplify()` | unevaluated | `0 provided not floor(x) = 0 and not x in ZZ` | +| `"derivative(abs(min(x, 1)), x)".ToEntity().Simplify()` | unevaluated | `sgn(min(x, 1)) * (1 - sgn(x - 1)) / 2 provided not min(x, 1) = 0 and not x - 1 = 0` | + ### Membership and divisibility compile **Not silent: what was refused is compiled.** `x in S` and `a divides b` were refused by both compilers with diff --git a/Sources/.editorconfig b/Sources/.editorconfig index f19e17474..d3925edc9 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -1013,5 +1013,8 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Common/DivisionByZeroOverDoubleTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Common/RefuseNonRealCompilationTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Common/BranchCutCompilationTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Convenience/CompilationExtensions.cs b/Sources/AngouriMath/Convenience/CompilationExtensions.cs index 5e4b42e48..8a0b07f3d 100644 --- a/Sources/AngouriMath/Convenience/CompilationExtensions.cs +++ b/Sources/AngouriMath/Convenience/CompilationExtensions.cs @@ -53,6 +53,11 @@ public static partial class AngouriMathExtensions /// 3 /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static TDelegate Compile(this string @this, CompilationProtocol protocol, Type returnType, IEnumerable<(Type type, Variable variable)> typesAndNames) where TDelegate : Delegate => IntoLinqCompiler.Compile(@this, returnType, protocol, typesAndNames); @@ -86,6 +91,11 @@ public static TDelegate Compile(this string @this, CompilationProtoco /// 10 /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1) }); @@ -126,6 +136,11 @@ public static Func Compile(this string @this, Variable v /// 30 /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) }); @@ -172,6 +187,11 @@ public static Func Compile(this string @this /// 60 /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2, Variable var3) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) }); @@ -224,6 +244,11 @@ public static Func Compile(this /// 100 /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2, Variable var3, Variable var4) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) }); @@ -282,6 +307,11 @@ public static Func Compile /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2, Variable var3, Variable var4, Variable var5) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) }); @@ -346,6 +376,11 @@ public static Func Compile /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2, Variable var3, Variable var4, Variable var5, Variable var6) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) , (typeof(TIn6), var6) }); @@ -416,6 +451,11 @@ public static Func Compile /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2, Variable var3, Variable var4, Variable var5, Variable var6, Variable var7) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) , (typeof(TIn6), var6) , (typeof(TIn7), var7) }); @@ -492,6 +532,11 @@ public static Func Compile /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func Compile(this string @this, Variable var1, Variable var2, Variable var3, Variable var4, Variable var5, Variable var6, Variable var7, Variable var8) => IntoLinqCompiler.Compile>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) , (typeof(TIn6), var6) , (typeof(TIn7), var7) , (typeof(TIn8), var8) }); diff --git a/Sources/AngouriMath/Convenience/CompilationExtensions.tt b/Sources/AngouriMath/Convenience/CompilationExtensions.tt index 4f9c27455..61c3a3804 100644 --- a/Sources/AngouriMath/Convenience/CompilationExtensions.tt +++ b/Sources/AngouriMath/Convenience/CompilationExtensions.tt @@ -53,6 +53,11 @@ namespace AngouriMath.Extensions /// 3 /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static TDelegate Compile(this string @this, CompilationProtocol protocol, Type returnType, IEnumerable<(Type type, Variable variable)> typesAndNames) where TDelegate : Delegate => IntoLinqCompiler.Compile(@this, returnType, protocol, typesAndNames); @@ -104,6 +109,11 @@ namespace AngouriMath.Extensions /// <#= total #> /// /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public static Func<<# for(var t=1;t<=i;t++){ #>TIn<#= t #>, <# } #>TOut> Compile<<# for(var t=1;t<=i;t++){ #>TIn<#= t #>, <# } #>TOut>(this string @this, Variable var1<# for(var t=2; t<=i; t++){ #>, Variable var<#= t #><# } #>) => IntoLinqCompiler.CompileTIn<#= t #>, <# } #>TOut>>(@this, typeof(TOut), new(), new[] { (typeof(TIn1), var1)<# for(var t=2;t<=i;t++){ #>, (typeof(TIn<#= t #>), var<#= t #>) <# } #> }); diff --git a/Sources/AngouriMath/Convenience/MathS.cs b/Sources/AngouriMath/Convenience/MathS.cs index c1537e472..2cfee6d72 100644 --- a/Sources/AngouriMath/Convenience/MathS.cs +++ b/Sources/AngouriMath/Convenience/MathS.cs @@ -6285,6 +6285,43 @@ internal static EDecimal DowncastingTolerance /// public static Setting AllowNewton { get; } = true; + /// + /// Whether compiling into a function that returns or refuses an + /// expression that is not real wherever it is defined. Off by default. + /// + /// + /// Compiled over the reals, every value the function computes on the way is a real number. So an expression + /// whose value at a real argument is not real, or is real only by way of a value that is not -- sqrt(x) + /// at -4, sin(arcsin(x)) at 2 -- evaluates to NaN there, as it does where it has no value at all, as + /// 1/x at 0. With this off, that is how such an expression compiles, and a NaN from the compiled + /// function means that the expression has no value there or that its computation left the reals. With it on, + /// an expression that cannot be shown to be real wherever it is defined is refused with + /// , and a NaN means only that there is no value. The + /// showing is structural and so conservative: sqrt(x) and ln(x) are refused, though both are + /// real for positive x. Compile over for the value itself. + /// #2286 + /// + /// + /// + /// using static AngouriMath.MathS; + /// + /// Console.WriteLine("sin(arcsin(x))".Compile<double, double>("x")(2)); + /// + /// using var _ = Settings.RefuseNonRealCompilation.Set(true); + /// + /// Console.WriteLine("x / (1 + x^2)".Compile<double, double>("x")(2)); + /// try { "sin(arcsin(x))".Compile<double, double>("x"); } + /// catch (AngouriMath.Core.Exceptions.UncompilableNodeException) { Console.WriteLine("refused"); } + /// + /// Prints + /// + /// NaN + /// 0.4 + /// refused + /// + /// + public static Setting RefuseNonRealCompilation { get; } = false; + /// /// Criteria for simplifier so you could control which expressions are considered easier by you. /// The higher the value - the more complicated an expression is. diff --git a/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.cs b/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.cs index 6237b3a93..c4c3ef572 100644 --- a/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.cs +++ b/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.cs @@ -37,6 +37,11 @@ partial record Entity /// /// Returnes a natively compiled expression of type /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public TDelegate Compile(CompilationProtocol protocol, Type returnType, IEnumerable<(Type type, Variable variable)> typesAndNames) where TDelegate : Delegate => IntoLinqCompiler.Compile(this, returnType, protocol, typesAndNames); @@ -57,6 +62,11 @@ public TDelegate Compile(CompilationProtocol protocol, Type returnTyp /// /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1) }); @@ -84,6 +94,11 @@ public Func Compile(Variable var1) /// /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) }); @@ -117,6 +132,11 @@ public Func Compile(Variable var1, Variable /// /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2, Variable var3) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) }); @@ -156,6 +176,11 @@ public Func Compile(Variable var /// /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2, Variable var3, Variable var4) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) }); @@ -201,6 +226,11 @@ public Func Compile( /// /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2, Variable var3, Variable var4, Variable var5) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) }); @@ -252,6 +282,11 @@ public Func Compile /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2, Variable var3, Variable var4, Variable var5, Variable var6) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) , (typeof(TIn6), var6) }); @@ -309,6 +344,11 @@ public Func Compile /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2, Variable var3, Variable var4, Variable var5, Variable var6, Variable var7) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) , (typeof(TIn6), var6) , (typeof(TIn7), var7) }); @@ -372,6 +412,11 @@ public Func Compile /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func Compile(Variable var1, Variable var2, Variable var3, Variable var4, Variable var5, Variable var6, Variable var7, Variable var8) => IntoLinqCompiler.Compile>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1), (typeof(TIn2), var2) , (typeof(TIn3), var3) , (typeof(TIn4), var4) , (typeof(TIn5), var5) , (typeof(TIn6), var6) , (typeof(TIn7), var7) , (typeof(TIn8), var8) }); diff --git a/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.tt b/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.tt index 5f41acc5b..aa58c4713 100644 --- a/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.tt +++ b/Sources/AngouriMath/Functions/Compilation/Compile.Linq.Definition.tt @@ -37,6 +37,11 @@ namespace AngouriMath /// /// Returnes a natively compiled expression of type /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public TDelegate Compile(CompilationProtocol protocol, Type returnType, IEnumerable<(Type type, Variable variable)> typesAndNames) where TDelegate : Delegate => IntoLinqCompiler.Compile(this, returnType, protocol, typesAndNames); @@ -62,6 +67,11 @@ namespace AngouriMath /// /// Returns a natively-compiled delegate /// + /// + /// Into a function that returns or , a value that is not real is NaN, and + /// so is every value computed from one, though the expression may be real there: sin(arcsin(x)) at 2. + /// refuses such an expression instead. + /// public Func<<# for(var t=1;t<=i;t++){ #>TIn<#= t #>, <# } #>TOut> Compile<<# for(var t=1;t<=i;t++){ #>TIn<#= t #>, <# } #>TOut>(Variable var1<# for(var t=2; t<=i; t++){ #>, Variable var<#= t #><# } #>) => IntoLinqCompiler.CompileTIn<#= t #>, <# } #>TOut>>(this, typeof(TOut), new(), new[] { (typeof(TIn1), var1)<# for(var t=2;t<=i;t++){ #>, (typeof(TIn<#= t #>), var<#= t #>) <# } #> }); diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/IntoLinqCompiler.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/IntoLinqCompiler.cs index 9ac042250..e6a981f99 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/IntoLinqCompiler.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/IntoLinqCompiler.cs @@ -32,6 +32,10 @@ internal static TDelegate Compile( expr = expr.InnerSimplified; expr = CompiledMembership.Rewritten(expr); StatementPositions.Check(expr); + if (MathS.Settings.RefuseNonRealCompilation && returnType is { } real && IsRealType(real) + && typesAndNames.All(c => IsRealType(c.type)) && !IsRealWhereverDefined(expr, typesAndNames)) + throw new UncompilableNodeException( + $"`{expr.Stringize()}` is not real wherever it is defined, and MathS.Settings.RefuseNonRealCompilation is on: compile it over Complex"); var subexpressionsCache = typesAndNames.ToDictionary(c => (Entity)c.variable, c => Expression.Parameter(c.type)); var functionArguments = subexpressionsCache.Select(c => c.Value).ToArray(); // copying @@ -68,6 +72,23 @@ or NotSupportedException } } + private static bool IsRealType(Type type) + => type == typeof(double) || type == typeof(float) || type == typeof(int) || type == typeof(long) + || type == typeof(System.Numerics.BigInteger); + + /// + /// Whether is real wherever it is defined with every argument real, by + /// . That reads one variable as real, and reads its shapes + /// alone and never the values, so each argument is written as one fresh variable and its verdict is the + /// verdict for them all. https://github.com/asc-community/AngouriMath/issues/2286 + /// + private static bool IsRealWhereverDefined(Entity expr, IEnumerable<(Type type, Variable variable)> typesAndNames) + { + var real = Variable.CreateTemp(expr.Vars); + var renamed = typesAndNames.Aggregate(expr, (e, argument) => e.Substitute(argument.variable, real)); + return Functions.TreeAnalyzer.IsRealValued(renamed, real); + } + internal static Expression BuildTree( Entity expr, Dictionary cachedSubexpressions, diff --git a/Sources/AngouriMath/Functions/TreeAnalyzer/RealValued.cs b/Sources/AngouriMath/Functions/TreeAnalyzer/RealValued.cs index a13eb8c26..5345ef5ff 100644 --- a/Sources/AngouriMath/Functions/TreeAnalyzer/RealValued.cs +++ b/Sources/AngouriMath/Functions/TreeAnalyzer/RealValued.cs @@ -67,6 +67,14 @@ internal static bool IsRealValued(Entity expr, Variable x) or Arctanf or Arccotanf or Signumf or Erff or Erfcf or Erfif or Eif or Sif or Shif or Conjugatef: return IsRealValued(expr.DirectChildren[0], x); + // The integer below, above or nearest to a real number is real, and so is the larger or the smaller + // of two. https://github.com/asc-community/AngouriMath/issues/2286 + case Floorf or Ceilf or Roundf: + return IsRealValued(expr.DirectChildren[0], x); + case Maxf(var left, var right): + return IsRealValued(left, x) && IsRealValued(right, x); + case Minf(var left, var right): + return IsRealValued(left, x) && IsRealValued(right, x); // A polygamma function of a whole order is real on the real line between its poles. case Polygammaf(var order, var argument) when !order.ContainsNode(x): return IsRealValued(argument, x); diff --git a/Sources/Tests/UnitTests/Common/PublicApi.txt b/Sources/Tests/UnitTests/Common/PublicApi.txt index 935c06bcb..d4ab90591 100644 --- a/Sources/Tests/UnitTests/Common/PublicApi.txt +++ b/Sources/Tests/UnitTests/Common/PublicApi.txt @@ -3498,6 +3498,7 @@ AngouriMath.MathS+Settings.MaxExpansionTermCount { } : AngouriMath.Convenience.S AngouriMath.MathS+Settings.NewtonSolver { } : AngouriMath.Convenience.Setting AngouriMath.MathS+Settings.PrecisionErrorCommon { } : AngouriMath.Convenience.Setting AngouriMath.MathS+Settings.PrecisionErrorZeroRange { } : AngouriMath.Convenience.Setting +AngouriMath.MathS+Settings.RefuseNonRealCompilation { } : AngouriMath.Convenience.Setting AngouriMath.MathS+UnsafeAndInternal.AreEqualNumerically(AngouriMath.Entity, AngouriMath.Entity) : System.Boolean AngouriMath.MathS+UnsafeAndInternal.AreEqualNumerically(AngouriMath.Entity, AngouriMath.Entity, AngouriMath.Entity[]) : System.Boolean AngouriMath.MathS+UnsafeAndInternal.CheckPoints { } : AngouriMath.Convenience.Setting diff --git a/Sources/Tests/UnitTests/Common/RefuseNonRealCompilationTest.cs b/Sources/Tests/UnitTests/Common/RefuseNonRealCompilationTest.cs new file mode 100644 index 000000000..4fd932cf7 --- /dev/null +++ b/Sources/Tests/UnitTests/Common/RefuseNonRealCompilationTest.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System.Numerics; +using AngouriMath; +using AngouriMath.Core.Exceptions; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Common +{ + /// + /// Compiled over , a value that is not real is NaN, and so is everything computed from it, though + /// the expression may be real there: sin(arcsin(x)) at 2 is NaN. That is documented, and + /// refuses such an expression instead, so that a NaN means only + /// that there is no value. + /// #2286 + /// + [Trait("Area", "Common")] + public sealed class RefuseNonRealCompilationTest + { + [Theory] + [InlineData("sin(arcsin(x))", 2)] + [InlineData("sqrt(x)^2", -4)] + public void ByDefaultAValueOffTheRealsIsNaN(string expression, double at) + => Assert.True(double.IsNaN(expression.Compile("x")(at))); + + [Theory] + [InlineData("sin(arcsin(x))")] + [InlineData("sqrt(x)")] + [InlineData("ln(x)")] + [InlineData("x^(1/3)")] + public void WhenRefusingWhatIsNotRealItIsRefused(string expression) + { + using var _ = MathS.Settings.RefuseNonRealCompilation.Set(true); + Assert.Throws(() => expression.Compile("x")); + Assert.Throws(() => expression.Compile("x")); + } + + [Fact] + public void WhenRefusingAPowerOfTwoArgumentsIsRefused() + { + using var _ = MathS.Settings.RefuseNonRealCompilation.Set(true); + Assert.Throws(() => "x^y".Compile("x", "y")); + } + + [Theory] + [InlineData("x / (1 + x^2)", 2, 0.4)] + [InlineData("floor(x) + max(x, 1)", 2.5, 4.5)] + [InlineData("abs(x) + e^x - e^x", -3, 3)] + [InlineData("x^3 - 2 * x", 2, 4)] + public void WhenRefusingWhatIsRealIsCompiled(string expression, double at, double value) + { + using var _ = MathS.Settings.RefuseNonRealCompilation.Set(true); + Assert.Equal(value, expression.Compile("x")(at), 12); + } + + [Fact] + public void WhenRefusingADivisionByZeroIsStillNaN() + { + using var _ = MathS.Settings.RefuseNonRealCompilation.Set(true); + Assert.True(double.IsNaN("1/x".Compile("x")(0))); + } + + [Fact] + public void WhenRefusingTheComplexCompilerAndAStatementAreUnaffected() + { + using var _ = MathS.Settings.RefuseNonRealCompilation.Set(true); + Assert.Equal(new Complex(2, 0), "sqrt(x)".Compile("x")(4)); + Assert.True("sqrt(x) > 0".Compile("x")(4)); + } + } +}