Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 51 additions & 1 deletion Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/// <summary><c>base^power</c> by the polar form, for a base that is not 0 with modulus <paramref name="rho"/>.</summary>
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);
Expand All @@ -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);
}

/// <summary>
/// The most bits an exact power is built to: <c>3^(10^7)</c>, at 15.8 million, takes three seconds,
/// and each doubling of the exponent doubles the size and more than doubles the time.
/// </summary>
[ConstantField] private static readonly EInteger MaxExactPowerBits = EInteger.FromInt32(1 << 24);

/// <summary>
/// About how many bits an exact <paramref name="base"/> to the whole <paramref name="power"/> 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.
/// </summary>
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());
}

/// <summary>e.g. Log(2, 32) = 5</summary>
/// <param name="base">Log's base, log(base, x) is a number y such that base^y = x</param>
/// <param name="x">The number of which we want to get its base power</param>
Expand Down
19 changes: 17 additions & 2 deletions Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
/// Whether <paramref name="value"/> is an infinity that only the decimals made: an exact power too
/// large to build is evaluated as a decimal, which is <c>+oo</c> past the context's range, and an
/// infinity counts as exact, so that <c>2^(10^10)</c>, and every expression holding it, was
/// simplified to <c>+oo</c>. Asked only of a value that is not finite.
/// </summary>
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));

/// <summary>
/// Whether <paramref name="expr"/> 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.
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
63 changes: 63 additions & 0 deletions Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// An exact number to a whole power was built exactly however large, so <c>46655^(10^20)</c>, 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: <c>+oo</c> 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 <c>(x^x - 1)^(e^(e^x))</c> at 6; no issue was filed.
/// </summary>
[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<Entity> 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;
}
}
}
Loading