diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs index 1b9651440..e2931bcff 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs @@ -557,18 +557,27 @@ public static Complex Pow(Complex @base, Complex power) // cube was three multiplications and a tenth power was nine, each with the // rational search on its result, and the recursion was linear in the exponent // where it should be logarithmic. + // + // A loop over the exponent's bits, from the top, rather than a recursion on its + // half: the recursion was one frame a bit, and a power of -1, i or -i is never + // too large to build, so (-1)^(2^20000) took 20000 frames and overflowed the + // stack of a thread-pool thread, which ends the process. The multiplications are + // the recursion's, in its order: the half truncates toward zero, so a negative + // exponent is the bits of its magnitude, each one multiplying by 1 / num. static Complex BinaryIntPow(Complex num, EInteger val) { if (val.IsZero) return 1; - if (val.Equals(1)) - return num; - if (val.Equals(-1)) - return 1 / num; - var divRem = val.DivRem(2); // divRem[0] == val / 2, divRem[1] == val % 2 - var half = BinaryIntPow(num, divRem[0]); - var squared = half * half; - return divRem[1].IsZero ? squared : squared * BinaryIntPow(num, divRem[1]); + var magnitude = val.Abs(); + var factor = val.Sign < 0 ? 1 / num : num; + var result = factor; + for (var bit = magnitude.GetUnsignedBitLengthAsInt64() - 2; bit >= 0; bit--) + { + result *= result; + if (magnitude.GetSignedBit(bit)) + result *= factor; + } + return result; } // A complex whose imaginary part is exactly zero is the real number it is, and // takes the real paths below: with the downcasting off, Complex.Create keeps diff --git a/Sources/Tests/UnitTests/Core/AWholePowerOfAUnitTest.cs b/Sources/Tests/UnitTests/Core/AWholePowerOfAUnitTest.cs new file mode 100644 index 000000000..ae403016b --- /dev/null +++ b/Sources/Tests/UnitTests/Core/AWholePowerOfAUnitTest.cs @@ -0,0 +1,50 @@ +// +// 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.Threading; +using Xunit; + +namespace AngouriMath.Tests.Core +{ + /// + /// A whole power was built by a recursion on the half of the exponent, one frame for each of its bits. A power of + /// -1, i or -i is never too large to build, so (-1)^(2^20000) took 20000 frames and overflowed the stack of a + /// thread-pool thread, 1.5 MB on Linux, which ends the process rather than throwing. Found by limitcheck at + /// DEPTH=3, SEED=28, evaluating a sample point; no issue was filed. + /// + [Trait("Area", "Core")] + public sealed class AWholePowerOfAUnitTest + { + // A quarter of a megabyte: smaller than any thread the library is run on, and enough that the recursion + // overflowed on these exponents, of 20000 bits and more, and ended the test host. + private const int SmallStack = 256 * 1024; + + [Theory] + [InlineData("(-1)^(2^20000)", "1")] + [InlineData("(-1)^(2^20000 + 1)", "-1")] + [InlineData("i^(3^20000)", "i")] + [InlineData("(-i)^(-(2^20001) - 3)", "-i")] + [InlineData("(-1)^(-(2^20000) - 1)", "-1")] + public void IsItsCycleOnASmallStack(string input, string expected) + { + Entity? value = null; + var thread = new Thread(() => value = MathS.FromString(input).EvalNumerical(), SmallStack); + thread.Start(); + thread.Join(); + Assert.Equal(MathS.FromString(expected).EvalNumerical(), value); + } + + // The multiplications are the recursion's, in its order, so a decimal power keeps every digit it had. + [Theory] + [InlineData("(1.1 + 0.2i)^(-13)", "-0.16276672053742730437722112 - 0.16876781705731410143215616i")] + [InlineData("(0.3 + 0.4i)^(-9)", "-241.716953088 - 451.350102016i")] + [InlineData("2.5^(-37)", "137438953472/72759576141834259033203125")] + [InlineData("i^(-7)", "i")] + public void IsWhatItWas(string input, string expected) + => Assert.Equal(MathS.FromString(expected).EvalNumerical(), MathS.FromString(input).EvalNumerical()); + } +}