diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 302c211bd..13ca3f066 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -380,6 +380,22 @@ and a number that passes every round is left open, since passing is not a proof
| `"(10^30 + 1) in PP".ToEntity().Evaled` | `1000000000000000000000000000001 in PP` | `False` |
| `"(10^30 + 57) in PP".ToEntity().Evaled` | `1000000000000000000000000000057 in PP` | `1000000000000000000000000000057 in PP` |
+### An exact power too large to build is the decimal
+
+**Not silent: what did not return now answers.** An exact number to a whole power was built exactly however large it
+was. `46655^(10^20)` has 4.7e20 digits, and its evaluation neither returned nor stopped when cancelled. Nor did a
+`Simplify` of anything holding it. Past sixteen million bits, about five million digits, a power is now evaluated as the
+decimal, as the same power with a half added already was: `+oo` past the context's range, `-oo` for a negative base to
+an odd power, and `0` below the range. An exact simplification leaves it as written, since the infinity is the
+decimal's. Each of -1, i and -i keeps its exact power. No issue was filed; a check of compiled expressions against
+evaluation found it.
+
+| Input | Was (2.6.0) | Now |
+|---|---|---|
+| `"46655^(10^20)".EvalNumerical()` | does not return | `+oo` |
+| `"(5/2)^(10^7)".EvalNumerical()` | does not return | `+oo` |
+| `"2^(10^10)".Simplify()` | does not return | `2 ^ 10000000000` |
+
### A remainder of two ratios is exact
**Silent: a remainder that came back as the divisor, or as NaN, is the remainder.** `%` on `Real`, `mod` and evaluation
diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs
index bb7382737..1070164b5 100644
--- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs
+++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs
@@ -586,6 +586,21 @@ static Complex BinaryIntPow(Complex num, EInteger val)
return Integer.Zero;
return infinite.IsNegative && !wholeOfInfinity.IsEven ? Real.NegativeInfinity : Real.PositiveInfinity;
}
+ // An exact power past MaxExactPowerBits is not built: 46655^(10^20) would have 4.7e20
+ // digits, and the multiplications for it never returned or stopped when cancelled, so
+ // a simplification of the expression did not either. It is the decimal, as the same
+ // power with a half added is: +oo past the context's range, 0 below it.
+ if (@base.IsFinite && pow is { } && IsTooLargeToBuild(@base, pow))
+ {
+ var largeContext = MathS.Settings.DecimalPrecisionContext;
+ if (@base is Real { EDecimal: var realOfLarge })
+ {
+ var magnitude = PowerOfReals(realOfLarge.Abs(), EDecimal.FromEInteger(pow), largeContext);
+ return Real.CreateAtScale(realOfLarge.IsNegative && !pow.IsEven ? magnitude.Negate() : magnitude, EDecimal.Zero);
+ }
+ return ByThePolarForm(@base.RealPart.EDecimal, @base.ImaginaryPart.EDecimal, @base.Abs().EDecimal,
+ EDecimal.FromEInteger(pow), EDecimal.Zero, largeContext);
+ }
if (@base.IsFinite && pow is { })
{
// A real base that is not exact -- a decimal, which is what a numerical
@@ -680,7 +695,13 @@ static Complex BinaryIntPow(Complex num, EInteger val)
if (baseReal.IsZero && baseImaginary.IsZero)
return Integer.Zero;
- var rho = @base.Abs().EDecimal;
+ return ByThePolarForm(baseReal, baseImaginary, @base.Abs().EDecimal, powerReal, powerImaginary, context);
+ }
+
+ /// base^power by the polar form, for a base that is not 0 with modulus .
+ private static Complex ByThePolarForm(EDecimal baseReal, EDecimal baseImaginary, EDecimal rho,
+ EDecimal powerReal, EDecimal powerImaginary, EContext context)
+ {
var theta = baseImaginary.Arctan2(baseReal, context);
var newRho = powerReal.MultiplyAndAdd(theta, powerImaginary.Multiply(rho.NaturalLogarithm(context), context), context);
var t = PowerOfReals(rho, powerReal, context).Multiply(powerImaginary.Multiply(-theta, context).Exponential(context), context);
@@ -690,6 +711,35 @@ static Complex BinaryIntPow(Complex num, EInteger val)
return Complex.CreateAtScales(t.Multiply(newRho.Cos(context), context), t.Multiply(newRho.Sin(context), context), modulusScale, modulusScale);
}
+ ///
+ /// The most bits an exact power is built to: 3^(10^7), at 15.8 million, takes three seconds,
+ /// and each doubling of the exponent doubles the size and more than doubles the time.
+ ///
+ [ConstantField] private static readonly EInteger MaxExactPowerBits = EInteger.FromInt32(1 << 24);
+
+ ///
+ /// About how many bits an exact to the whole has: for
+ /// each part of the base, the bits of its numerator and of its denominator past the first, the larger
+ /// part's, and one more where both parts are not 0, times the power. Each of -1, i and -i has none,
+ /// and a power of one is as small as it is. A base that is not exact is 0, being a decimal.
+ ///
+ internal static bool IsTooLargeToBuild(Complex @base, EInteger power)
+ => ExactPowerBits(@base, power).CompareTo(MaxExactPowerBits) > 0;
+
+ private static EInteger ExactPowerBits(Complex @base, EInteger power)
+ {
+ if (@base.RealPart is not Rational { ERational: var re } || @base.ImaginaryPart is not Rational { ERational: var im })
+ return EInteger.Zero;
+ static EInteger Bits(ERational part)
+ => part.IsZero ? EInteger.Zero
+ : part.Numerator.Abs().GetUnsignedBitLengthAsEInteger().Subtract(1)
+ .Add(part.Denominator.GetUnsignedBitLengthAsEInteger().Subtract(1));
+ var bits = Bits(re).CompareTo(Bits(im)) >= 0 ? Bits(re) : Bits(im);
+ if (!re.IsZero && !im.IsZero)
+ bits = bits.Add(1);
+ return bits.Multiply(power.Abs());
+ }
+
/// e.g. Log(2, 32) = 5
/// Log's base, log(base, x) is a number y such that base^y = x
/// The number of which we want to get its base power
diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs
index b5d0ccfa7..487682612 100644
--- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs
+++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs
@@ -55,6 +55,19 @@ private static bool IsAZeroOnlyRoundingMade(Entity node, Entity value)
=> value is Integer { IsZero: true }
&& (node is Mulf or Divf or Powf && CannotBeZero(node) || node is Sumf or Minusf && SignOfANonzero(node) is not null);
+ ///
+ /// Whether is an infinity that only the decimals made: an exact power too
+ /// large to build is evaluated as a decimal, which is +oo past the context's range, and an
+ /// infinity counts as exact, so that 2^(10^10), and every expression holding it, was
+ /// simplified to +oo. Asked only of a value that is not finite.
+ ///
+ private static bool IsAnInfinityOnlyRoundingMade(Entity node, Entity value)
+ => value is Complex { IsFinite: false } && !value.IsNaN
+ && node.Nodes.Any(static part => part is Powf(var @base, var exponent)
+ && @base.Evaled is Complex { IsExact: true, IsFinite: true } exactBase
+ && exponent.Evaled is Integer { EInteger: var power }
+ && Number.IsTooLargeToBuild(exactBase, power));
+
///
/// Whether is a number or a constant that is not zero, or a product,
/// a quotient or a power of those with a finite divisor or exponent.
@@ -128,7 +141,8 @@ private Entity ExpandOnTwoArguments(
bool propagateSet = true,
bool settlesNaN = false)
{
- if (isExact && this.Evaled is (Number { IsExact: true } or Boolean) and var n && !IsAZeroOnlyRoundingMade(this, n))
+ if (isExact && this.Evaled is (Number { IsExact: true } or Boolean) and var n && !IsAZeroOnlyRoundingMade(this, n)
+ && !IsAnInfinityOnlyRoundingMade(this, n))
return n;
left = left.InnerSimplified(isExact);
right = right.InnerSimplified(isExact);
@@ -151,7 +165,8 @@ Entity ops(Entity a, Entity b)
{
if (operation(a, b) is { } res)
return res;
- if (isExact && defaultCtor(this, a, b) is var built && built.Evaled is Number { IsExact: true } n && !IsAZeroOnlyRoundingMade(built, n))
+ if (isExact && defaultCtor(this, a, b) is var built && built.Evaled is Number { IsExact: true } n && !IsAZeroOnlyRoundingMade(built, n)
+ && !IsAnInfinityOnlyRoundingMade(built, n))
return n;
return defaultCtor(this, a, b);
}
diff --git a/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs b/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs
new file mode 100644
index 000000000..df1204b96
--- /dev/null
+++ b/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs
@@ -0,0 +1,63 @@
+//
+// 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;
+using System.Threading.Tasks;
+using AngouriMath.Extensions;
+using Xunit;
+using static AngouriMath.Entity;
+using static AngouriMath.Entity.Number;
+
+namespace AngouriMath.Tests.Core
+{
+ ///
+ /// An exact number to a whole power was built exactly however large, so 46655^(10^20), which has
+ /// 4.7e20 digits, neither returned nor stopped when cancelled, and nor did a simplification of anything
+ /// holding it. Past about sixteen million bits the power is now evaluated as the decimal, as the same
+ /// power with a half added already was: +oo past the context's range. An exact simplification
+ /// leaves it as written, since the infinity is the decimal's and not the power's. Found by compilesweep,
+ /// on (x^x - 1)^(e^(e^x)) at 6; no issue was filed.
+ ///
+ [Trait("Area", "Core")]
+ public sealed class AnExactPowerTooLargeToBuildTest
+ {
+ [Theory]
+ [InlineData("46655^(10^20)", "+oo")]
+ [InlineData("46655^(e^(e^6))", "+oo")]
+ [InlineData("(-3)^(10^20 + 1)", "-oo")]
+ [InlineData("(-3)^(10^20)", "+oo")]
+ [InlineData("(5/2)^(10^7)", "+oo")]
+ // Each of -1, i and -i keeps its exact power, however large.
+ [InlineData("i^(10^20)", "1")]
+ [InlineData("(-1)^(10^20 + 1)", "-1")]
+ [InlineData("(-i)^(10^20 + 3)", "i")]
+ public void IsEvaluated(string input, string expected)
+ => Assert.Equal(expected.ToEntity().Evaled, Within(() => input.ToEntity().Evaled, input));
+
+ [Theory]
+ [InlineData("2^(10^10)")]
+ [InlineData("46655^(10^20) + 1")]
+ [InlineData("(1/3)^(10^20)")]
+ public void AnExactSimplificationLeavesItAsWritten(string input)
+ {
+ var simplified = Within(() => input.ToEntity().InnerSimplified, input);
+ Assert.True(simplified is not Number, $"{input} was simplified to {simplified}");
+ Assert.True(Within(() => input.ToEntity().Simplify(), input) is not Number, $"{input} was simplified to a number");
+ }
+
+ [Fact]
+ public void APowerBelowTheBoundIsStillExact()
+ => Assert.Equal(Integer.Create(PeterO.Numbers.EInteger.One.ShiftLeft(1000000)), Within(() => "2^(10^6)".ToEntity().Evaled, "2^(10^6)"));
+
+ private static Entity Within(Func evaluation, string input)
+ {
+ var task = Task.Factory.StartNew(evaluation, TaskCreationOptions.LongRunning);
+ Assert.True(task.Wait(TimeSpan.FromSeconds(30)), $"{input} did not finish in thirty seconds");
+ return task.Result;
+ }
+ }
+}