From 830e844ff4c457d44d3e7163d098d353f265104e Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 15:04:16 +0000 Subject: [PATCH] A compiled power over double of a value that does not exist has none Compile and Compile took a power by Math.Pow, for which NaN^0 and 1^NaN are 1, so (x provided x > 1)^0 at 0.7 was 1, and ((x provided x > 1)^Im(x)) * sin(-2) was -0.909, where neither has a value. Evaluation has none, and FastExpression and the compilers over Complex have had none since #2225. The power is NaN now where either side is. A NaN over double also stands for a value that is not real, so (sqrt(x - 2))^0 at 0.7, which is 1, is NaN now, where it was 1 by Math.Pow's NaN^0. That is a decline where there was an answer by coincidence, as sqrt(x - 2) + 1 - sqrt(x - 2) was already a decline there; the other way round, a value where there is none, was a wrong answer. PowerOfNaNOverDoubleTest takes five powers with no value over double, by FastExpression and in evaluation, two over float, and four with a value. Found by work/compilesweep. Fixes #2250. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 15 ++++++ Sources/.editorconfig | 3 ++ .../Compilation/IntoLinq/MathAllMethods.cs | 6 ++- .../Compilation/IntoLinq/MathAllMethods.tt | 6 ++- .../Common/PowerOfNaNOverDoubleTest.cs | 52 +++++++++++++++++++ 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 Sources/Tests/UnitTests/Common/PowerOfNaNOverDoubleTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index fef54c9ea..5f7071841 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -507,6 +507,21 @@ anything else is `Complex.Pow` as before. At finite positive points the two agre | `"x^(-1)".ToEntity().Compile("x").Call(0)` | `(0, 0)` | `(NaN, 0)` | | `"x^0".ToEntity().Compile("x").Call(0)` | `(1, 0)` | `(NaN, 0)` | +### A compiled power over `double` of a value that does not exist has none + +**Silent: a power over `double` whose base or exponent is `NaN` is `NaN`, where a zero exponent or a base of `1` made +it `1`.** `Compile` and `Compile` took a power by `Math.Pow`, for which `NaN^0` and `1^NaN` are +`1`, so a power of a value that does not exist had one. FastExpression and the compilers over `Complex` have it as +`NaN`, as evaluation does ([#2250](https://github.com/asc-community/AngouriMath/issues/2250)). Over `double` a `NaN` +also stands for a value that is not real, so `(sqrt(x - 2))^0` at `0.7`, which is `1`, is `NaN` there now, as +`sqrt(x - 2) + 1 - sqrt(x - 2)` already was. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"(x provided x > 1)^0".ToEntity().Compile("x")(0.7)` | `1` | `NaN` | +| `"1^(x provided x > 1)".ToEntity().Compile("x")(0.7)` | `1` | `NaN` | +| `"x^0".ToEntity().Compile("x")(double.NaN)` | `1` | `NaN` | + ### 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/.editorconfig b/Sources/.editorconfig index 006adb650..74771b1ff 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -938,5 +938,8 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Common/RealPowerCompilationTest.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/PowerOfNaNOverDoubleTest.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/Functions/Compilation/IntoLinq/MathAllMethods.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs index eae82e858..303a20e86 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs @@ -38,10 +38,12 @@ public static BigInteger Log(BigInteger a, BigInteger b) public static System.Numerics.Complex Pow(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Core.Compilation.RealProducts.Power(a, b); + // A power of a number that does not exist, or to such a power, has no value, as in evaluation and the other + // compilers: Math.Pow takes NaN^0 and 1^NaN as 1. https://github.com/asc-community/AngouriMath/issues/2250 public static double Pow(double a, double b) - => Math.Pow(a, b); + => double.IsNaN(a) || double.IsNaN(b) ? double.NaN : Math.Pow(a, b); public static float Pow(float a, float b) - => (float)Math.Pow(a, b); + => float.IsNaN(a) || float.IsNaN(b) ? float.NaN : (float)Math.Pow(a, b); public static long Pow(long a, long b) => (long)Math.Pow(a, b); public static int Pow(int a, int b) diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt index a7c0117d5..42295f49b 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt @@ -63,10 +63,12 @@ namespace AngouriMath.Core.Compilation.IntoLinq public static System.Numerics.Complex Pow(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Core.Compilation.RealProducts.Power(a, b); + // A power of a number that does not exist, or to such a power, has no value, as in evaluation and the other + // compilers: Math.Pow takes NaN^0 and 1^NaN as 1. https://github.com/asc-community/AngouriMath/issues/2250 public static double Pow(double a, double b) - => Math.Pow(a, b); + => double.IsNaN(a) || double.IsNaN(b) ? double.NaN : Math.Pow(a, b); public static float Pow(float a, float b) - => (float)Math.Pow(a, b); + => float.IsNaN(a) || float.IsNaN(b) ? float.NaN : (float)Math.Pow(a, b); public static long Pow(long a, long b) => (long)Math.Pow(a, b); public static int Pow(int a, int b) diff --git a/Sources/Tests/UnitTests/Common/PowerOfNaNOverDoubleTest.cs b/Sources/Tests/UnitTests/Common/PowerOfNaNOverDoubleTest.cs new file mode 100644 index 000000000..74b7980c4 --- /dev/null +++ b/Sources/Tests/UnitTests/Common/PowerOfNaNOverDoubleTest.cs @@ -0,0 +1,52 @@ +// +// 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 AngouriMath; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Common +{ + /// + /// Compiled over , a power of a value that does not exist, or to such a power, has none, as in + /// evaluation and FastExpression, where made NaN^0 and 1^NaN 1. + /// #2250 + /// + [Trait("Area", "Common")] + public sealed class PowerOfNaNOverDoubleTest + { + [Theory] + [InlineData("(x provided x > 1)^0", 0.7)] + [InlineData("1^(x provided x > 1)", 0.7)] + [InlineData("((x provided x > 1)^Im(x)) * sin(-2)", 0.7)] + [InlineData("x^0", double.NaN)] + [InlineData("1^x", double.NaN)] + public void ItHasNoValue(string expression, double at) + { + var overDoubles = expression.ToEntity().Compile("x")(at); + Assert.True(double.IsNaN(overDoubles), $"{expression} at {at}: {overDoubles} over double"); + Assert.True(double.IsNaN(expression.ToEntity().Compile("x").Call(at).Real), $"{expression} at {at} by FastExpression"); + if (!double.IsNaN(at)) + Assert.True(expression.ToEntity().Substitute("x", at).Evaled.IsNaN, $"{expression} at {at} evaluates to a value"); + } + + [Fact] + public void OverFloatToo() + { + Assert.True(float.IsNaN("x^0".ToEntity().Compile("x")(float.NaN))); + Assert.True(float.IsNaN("1^x".ToEntity().Compile("x")(float.NaN))); + } + + [Theory] + [InlineData("x^0", 0.7, 1)] + [InlineData("1^x", 0.7, 1)] + [InlineData("x^2", -3, 9)] + [InlineData("x^(1/2)", 4, 2)] + public void WhereItHasOneItIsThatValue(string expression, double at, double value) + => Assert.Equal(value, expression.ToEntity().Compile("x")(at)); + } +}