diff --git a/Sources/AngouriMath/Numerics/IntervalEvaluation.cs b/Sources/AngouriMath/Numerics/IntervalEvaluation.cs index a406deb6a..aba50555d 100644 --- a/Sources/AngouriMath/Numerics/IntervalEvaluation.cs +++ b/Sources/AngouriMath/Numerics/IntervalEvaluation.cs @@ -375,6 +375,16 @@ private static ComplexInterval Evaluate(Entity expr) } } + /// + /// The numerator of where it is an exact fraction of odd denominator below 10, to which + /// Number.Pow raises a negative real as its real root; otherwise. + /// + internal static EInteger? TheRealRootIsTaken(Entity exponent) + => exponent is Number.Rational { ERational: var exact } and not Number.Integer + && exact.Denominator.Abs() is var q && !q.IsEven && q.CompareTo(EInteger.FromInt32(10)) < 0 + ? (exact.Denominator.Sign < 0 ? exact.Numerator.Negate() : exact.Numerator) + : null; + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static ComplexInterval EvaluateOtherThanArithmetic(Entity expr) { @@ -387,9 +397,21 @@ private static ComplexInterval EvaluateOtherThanArithmetic(Entity expr) case Variable variable when variable == MathS.e: return ComplexInterval.Real(Interval.Around(Math.E)); case Powf(var @base, var exponent): + { if (@base == MathS.e) return Evaluate(exponent).Exp(); - return Evaluate(@base).Pow(Evaluate(exponent)); + var evaluatedBase = Evaluate(@base); + // The real root where the library's power takes it: a negative real base and an exact exponent of + // odd denominator below 10, as (-8)^(1/3) is -2. Principal there, the derivative of (-x^3)^(1/3) + // was -e^(-2 i pi/3) at x > 0 where it is -1, and the check turned the right answer away. + // https://github.com/asc-community/AngouriMath/issues/2089 + if (TheRealRootIsTaken(exponent) is { } numerator && evaluatedBase.IsReal && evaluatedBase.Re.High < 0) + { + var modulus = ComplexInterval.Real(-evaluatedBase.Re).Pow(Evaluate(exponent)); + return numerator.IsEven ? modulus : -modulus; + } + return evaluatedBase.Pow(Evaluate(exponent)); + } case Sinf(var argument): return Evaluate(argument).Sin(); case Cosf(var argument): diff --git a/Sources/AngouriMath/Numerics/PreciseEvaluation.cs b/Sources/AngouriMath/Numerics/PreciseEvaluation.cs index 2eb168b50..02ae830d1 100644 --- a/Sources/AngouriMath/Numerics/PreciseEvaluation.cs +++ b/Sources/AngouriMath/Numerics/PreciseEvaluation.cs @@ -456,7 +456,19 @@ private PreciseComplexInterval EvaluateOtherThanArithmetic(Entity expr) case Variable variable when variable == MathS.e: return Real(E()); case Powf(var @base, var exponent): - return @base == MathS.e ? Exp(Evaluate(exponent)) : Pow(Evaluate(@base), Evaluate(exponent)); + { + if (@base == MathS.e) + return Exp(Evaluate(exponent)); + var evaluatedBase = Evaluate(@base); + // The real root where the library's power takes it, as in the doubles. + // https://github.com/asc-community/AngouriMath/issues/2089 + if (IntervalEvaluation.TheRealRootIsTaken(exponent) is { } numerator && evaluatedBase.IsReal && evaluatedBase.Re.High.Sign < 0) + { + var modulus = Pow(Real(evaluatedBase.Re.Negate()), Evaluate(exponent)); + return numerator.IsEven ? modulus : Negate(modulus); + } + return Pow(evaluatedBase, Evaluate(exponent)); + } case Sinf(var argument): return Sin(Evaluate(argument)); case Cosf(var argument): diff --git a/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs b/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs index e7c27a0be..f95ed2877 100644 --- a/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs +++ b/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs @@ -40,6 +40,8 @@ public sealed class IntervalEvaluationTest [InlineData("arcsec(3)")] [InlineData("arccsc(-2)")] [InlineData("(2/3)^(1/3)")] + [InlineData("(-8)^(1/3) + (-2)^(2/5)")] + [InlineData("(-3/2)^(-4/7) + (-5)^(1/2)")] [InlineData("(1 + 2i)^3")] [InlineData("1/(3 + 4i)")] [InlineData("e^(i * pi/4)")] @@ -182,6 +184,7 @@ public void TheSizeOfALargeComplexValueIsFinite() [InlineData("sin(1 + i) * cos(2 - 3i)")] [InlineData("ln(-2 + i) + arctan(2) - arcsin(1/3)")] [InlineData("(2/3)^(1/3) + sec(1) + csc(2)")] + [InlineData("(-8)^(1/3) + (-2)^(2/5) + (-3/2)^(-4/7) + (-5)^(1/2)")] [InlineData("arccot(-2) + arcsec(3) + arccsc(-2) + arccot(0)")] [InlineData("sin(10^6) + cos(10^6)")] [InlineData("sqrt(2) provided 1 < 2")] @@ -265,6 +268,21 @@ public void ADerivativeKeepingASpecialFunctionIsChecked(string antiderivative, s Assert.False(Functions.PartialFractions.HoldsAtSampledPoints(derivative, (integrand + " + 1/1000").ToEntity(), x)); } + /// + /// The real root where the library's power takes it, as (-8)^(1/3) is -2: the derivative of + /// (-x^3)^(1/3), -x^2 (-x^3)^(-2/3), is -1, and at the points right of 0 the check read it on + /// the principal branch, -e^(-2 i pi/3), and said it was not. + /// #2089 + /// + [Fact] + public void TheRealRootIsTheOneThePowerTakes() + { + var x = MathS.Var("x"); + var derivative = "(-x^3)^(1/3)".ToEntity().Differentiate(x); + Assert.True(Functions.PartialFractions.HoldsAtSampledPoints(derivative, "-1".ToEntity(), x)); + Assert.False(Functions.PartialFractions.HoldsAtSampledPoints(derivative, "-1 + 1/1000".ToEntity(), x)); + } + [Fact] public void AgreementHasThreeOutcomes() {