diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index fde4eef32..92d4fc1b7 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1239,21 +1239,36 @@ 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. +### An exact power too large to build is left written + +**Not silent: what did not return now answers, or stays written.** 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, such a power is now +left as written, by evaluation and by `Simplify` alike, and `EvalNumerical` refuses it with `CannotEvalException`. It +is finite and not zero, so it is never `+oo`, `-oo` or `0`. An expression holding it is settled where structure settles +it, as with `a - a` or `0 * a`, or where the exponents settle it, as with `2^m / 2^n`. A comparison is settled by the +signs, by the exponents for one positive base, or by the logarithms of the magnitudes where they differ by more than +their rounding. Anything else stays written. Each of -1, i and -i keeps its exact power. Measured on 2.6.0 with a +twenty-second limit. https://github.com/asc-community/AngouriMath/issues/1448 | 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^9)".ToEntity().Evaled` | does not return | `2 ^ 1000000000` | +| `"2^(-(10^9))".ToEntity().Evaled` | does not return | `2 ^ (-1000000000)` | +| `"46655^(10^20)".EvalNumerical()` | does not return | throws `CannotEvalException` | | `"2^(10^10)".Simplify()` | does not return | `2 ^ 10000000000` | +| `"2^(10^9) - 2^(10^9)".Simplify()` | does not return | `0` | +| `"x - x + 2^(10^9) - 2^(10^9)".Simplify()` | does not return | `0` | +| `"2^(10^9) / 2^(10^9 - 1)".Simplify()` | does not return | `2` | +| `"2^(10^9) * 2^(-(10^9))".Simplify()` | does not return | `1` | +| `"0 * 2^(10^9)".ToEntity().Evaled` | does not return | `0` | +| `"2^(10^9) > 2^(10^9 - 1)".ToEntity().Evaled` | does not return | `True` | +| `"2^(10^9) = 2^(10^9 - 1)".ToEntity().Evaled` | does not return | `False` | +| `"3^(10^9) > 2^(10^9)".ToEntity().Evaled` | does not return | `True` | +| `"2^(10^9) > 5".ToEntity().Evaled` | does not return | `True` | +| `"2^(-(10^9)) > 0".Simplify()` | does not return | `True` | +| `"2^(10^9) = 4^(5 * 10^8)".ToEntity().Evaled` | does not return | `2 ^ 1000000000 = 4 ^ 500000000` | +| `"forall n in ZZ* : 3^(n + 1) divides 2^(3^n) + 1".ToEntity().Evaled` | does not return | as written | ### The inverse of a symbolic matrix carries no condition of a pivot diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs index e2931bcff..64fd9024f 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs @@ -627,8 +627,10 @@ static Complex BinaryIntPow(Complex num, EInteger val) } // 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. + // a simplification of the expression did not either. Evaluating a power node leaves it + // written before this is reached (https://github.com/asc-community/AngouriMath/issues/1448); + // a caller of Pow itself gets the decimal, as the same power with a half added does: + // +oo past the context's range, 0 below it. if (@base.IsFinite && pow is { } && IsTooLargeToBuild(@base, pow)) { var largeContext = MathS.Settings.DecimalPrecisionContext; diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs index d051593ba..4974424df 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs @@ -409,6 +409,17 @@ private bool IsEvenRoot => Exponent is Rational and not Integer and Rational ratio && ratio.ERational.Denominator.Remainder(2).IsZero; + /// + /// Whether an exact to the rational is past the size an + /// exact power is built to, a fraction p / q read as the whole power just above |p / q|: a + /// q-th root makes the power no longer than that, and one that is not exact is a decimal of the + /// same magnitude. A whole exponent allocates nothing here, being asked of every exact power. + /// + internal static bool IsTooLargeToBuild(Complex @base, Rational exponent) + => exponent is Integer { EInteger: var whole } + ? Number.IsTooLargeToBuild(@base, whole) + : Number.IsTooLargeToBuild(@base, exponent.ERational.Numerator.Abs().Divide(exponent.ERational.Denominator).Add(PeterO.Numbers.EInteger.One)); + private static bool TryPower(Matrix m, int exp, out Entity res) { // Only a square matrix has powers; Pow throws for any other. And only one with @@ -461,6 +472,12 @@ protected override Entity InnerSimplify(bool isExact) => ? 0 : MathS.NaN : new Providedf(0, Re(x) > 0), + // An exact power too large to build stays a power. It is finite and not zero, and as + // the decimal it was +oo or 0, which then fed exact arithmetic: 2^(10^9) - 2^(10^9) + // was NaN, 0 * 2^(10^9) was NaN and 2^(10^9) > 2^(10^9 - 1) was False. Left written, + // what can be said of it is said by structure or by its exponent, and the rest is + // left undecided. https://github.com/asc-community/AngouriMath/issues/1448 + (Complex n1, Rational n2) when IsTooLargeToBuild(n1, n2) => null, (Complex n1, Complex n2) when !isExact => Number.Pow(n1, n2), // returns 1 for 0^0 and 0 for 0^(negative real part), we need to handle these cases above (Integer(1), { DomainCondition: var condition }) => Integer.One.Provided(condition), (var n1, Integer(-1)) => (1 / n1).InnerSimplified(isExact), diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs index 05ffd31a5..b93e5e296 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs @@ -324,6 +324,10 @@ protected override Entity InnerSimplify(bool isExact) // A size that is not a number is compared as a size, never by the difference // of two expressions: card(ZZ) - card(QQ) is not zero, and the two are equal. : AngouriMath.Core.Sets.Cardinality.IsSize(left) || AngouriMath.Core.Sets.Cardinality.IsSize(right) ? AngouriMath.Core.Sets.Cardinality.Compare(left, right, static sign => sign == 0, isExact) + // An exact power too large to build is not a constant, its value being left written, and is + // told apart from a number or another such power where its sign, exponent or magnitude decides it. + // https://github.com/asc-community/AngouriMath/issues/1448 + : OrderOfUnbuiltPowers(left, right) is { } order ? order == 0 : left.IsConstant && right.IsConstant ? (HasAnUnreadSize(left) || HasAnUnreadSize(right) ? null : ConstantsAreEqual(left, right)) : left is Set setLeft && right is Set setRight ? SetsAreEqual(setLeft, setRight, isExact) : null, @@ -347,6 +351,80 @@ protected override Entity InnerSimplify(bool isExact) _ => null, }; + /// + /// An exact power too large to build, which evaluation leaves written: an exact base that is not zero to a + /// rational exponent. for anything else. + /// https://github.com/asc-community/AngouriMath/issues/1448 + /// + private static (Number.Rational Base, Number.Rational Exponent)? AsAnUnbuiltPower(Entity expr) + => expr is Powf(Number.Rational { IsZero: false } @base, Number.Rational exponent) && Powf.IsTooLargeToBuild(@base, exponent) + ? (@base, exponent) : null; + + /// + /// The sign of a real power of an exact real base that is not zero, where its exponent decides it: positive + /// for a positive base, and for a negative one by the parity of a whole exponent. + /// + private static int? SignOfAnUnbuiltPower((Number.Rational Base, Number.Rational Exponent) power) + => power.Base.ERational.Sign > 0 ? 1 + : power.Exponent is Number.Integer { EInteger: var whole } ? (whole.IsEven ? 1 : -1) + : null; + + /// + /// The sign of left - right where either side is an exact power too large to build and the other is a + /// real number or another such power, decided without building either: two powers of one positive base by + /// their exponents, exactly, and otherwise by the signs and then by the logarithms of the magnitudes, where + /// those differ by more than the error of computing them. where nothing decides it, + /// which leaves the comparison written; in particular, two magnitudes that may be equal are never told + /// apart. As the decimals, these powers were +oo and 0, and 2^(10^9) > 2^(10^9 - 1) + /// was False. https://github.com/asc-community/AngouriMath/issues/1448 + /// + private static int? OrderOfUnbuiltPowers(Entity left, Entity right) + { + var (powerLeft, powerRight) = (AsAnUnbuiltPower(left), AsAnUnbuiltPower(right)); + if (powerLeft is null && powerRight is null) + return null; + if (powerLeft is { } l && powerRight is { } r && l.Base.ERational.Equals(r.Base.ERational) && l.Base.ERational.Sign > 0) + { + var byExponent = l.Exponent.ERational.CompareTo(r.Exponent.ERational); + return l.Base.ERational.CompareTo(ERational.One) > 0 ? byExponent : -byExponent; + } + if (SignAndLogarithm(left, powerLeft) is not var (signLeft, logLeft) || SignAndLogarithm(right, powerRight) is not var (signRight, logRight)) + return null; + // A power is not zero, so a side of the other sign, or zero, is decided by the signs alone. + if (signLeft != signRight || logLeft is null || logRight is null) + return signLeft.CompareTo(signRight); + // Each logarithm is within a relative 1e-99 of its value: a decimal side is rounded to a hundred digits, + // and the rest is computed to OrderContext's. So a difference past 1e-90 of their size is not rounding. + var difference = logLeft.Subtract(logRight, OrderContext); + var bound = logLeft.Abs().Add(logRight.Abs()).Add(EDecimal.One).Multiply(OrderTolerance); + if (difference.Abs().CompareTo(bound) <= 0) + return null; + return signLeft * difference.Sign; + } + + /// + /// The sign of an exact power too large to build, or of a finite real number, and the natural logarithm of + /// its magnitude, for zero. for anything else, and for a power + /// whose sign its exponent does not settle. + /// + private static (int Sign, EDecimal? Logarithm)? SignAndLogarithm(Entity side, (Number.Rational Base, Number.Rational Exponent)? power) + { + if (power is { } p) + return SignOfAnUnbuiltPower(p) is { } sign + ? (sign, p.Exponent.ERational.ToEDecimal(OrderContext) + .Multiply(p.Base.ERational.Abs().ToEDecimal(OrderContext).Log(OrderContext), OrderContext)) + : null; + if (side is Number number && AsAReal(number) is { EDecimal: { IsFinite: true } real }) + return real.IsZero ? (0, null) : (real.Sign, real.Abs().Log(OrderContext)); + return null; + } + + /// The working precision of : twenty digits past the decimals'. + [Core.ConstantField] private static readonly EContext OrderContext = EContext.ForPrecision(120); + + /// The relative difference of two logarithms below which leaves them undecided. + [Core.ConstantField] private static readonly EDecimal OrderTolerance = EDecimal.FromString("1e-90"); + partial record Greaterf { // Inequality comparisons are only defined for real numbers. @@ -361,6 +439,7 @@ protected override Entity InnerSimplify(bool isExact) (Number numLeft, Number numRight) when AsAReal(numLeft) is { } reLeft && AsAReal(numRight) is { } reRight => reLeft > reRight, (Number numLeft, Number numRight) => MathS.NaN, _ when AngouriMath.Core.Sets.Cardinality.IsSize(a) || AngouriMath.Core.Sets.Cardinality.IsSize(b) => AngouriMath.Core.Sets.Cardinality.Compare(a, b, static sign => sign > 0, isExact), + _ when OrderOfUnbuiltPowers(a, b) is { } sign => sign > 0, _ => null }, (@this, a, b) => ((Greaterf)@this).New(a, b), isExact); @@ -381,6 +460,7 @@ protected override Entity InnerSimplify(bool isExact) (Number numLeft, Number numRight) when AsAReal(numLeft) is { } reLeft && AsAReal(numRight) is { } reRight => reLeft >= reRight, (Number numLeft, Number numRight) => MathS.NaN, _ when AngouriMath.Core.Sets.Cardinality.IsSize(a) || AngouriMath.Core.Sets.Cardinality.IsSize(b) => AngouriMath.Core.Sets.Cardinality.Compare(a, b, static sign => sign >= 0, isExact), + _ when OrderOfUnbuiltPowers(a, b) is { } sign => sign >= 0, _ => null }, (@this, a, b) => ((GreaterOrEqualf)@this).New(a, b), isExact); @@ -401,6 +481,7 @@ protected override Entity InnerSimplify(bool isExact) (Number numLeft, Number numRight) when AsAReal(numLeft) is { } reLeft && AsAReal(numRight) is { } reRight => reLeft < reRight, (Number numLeft, Number numRight) => MathS.NaN, _ when AngouriMath.Core.Sets.Cardinality.IsSize(a) || AngouriMath.Core.Sets.Cardinality.IsSize(b) => AngouriMath.Core.Sets.Cardinality.Compare(a, b, static sign => sign < 0, isExact), + _ when OrderOfUnbuiltPowers(a, b) is { } sign => sign < 0, _ => null }, (@this, a, b) => ((Lessf)@this).New(a, b), isExact); @@ -420,6 +501,7 @@ protected override Entity InnerSimplify(bool isExact) (Number numLeft, Number numRight) when AsAReal(numLeft) is { } reLeft && AsAReal(numRight) is { } reRight => reLeft <= reRight, (Number numLeft, Number numRight) => MathS.NaN, _ when AngouriMath.Core.Sets.Cardinality.IsSize(a) || AngouriMath.Core.Sets.Cardinality.IsSize(b) => AngouriMath.Core.Sets.Cardinality.Compare(a, b, static sign => sign <= 0, isExact), + _ when OrderOfUnbuiltPowers(a, b) is { } sign => sign <= 0, _ => null }, (@this, a, b) => ((LessOrEqualf)@this).New(a, b), isExact); diff --git a/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs b/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs index df1204b96..fd69ff22b 100644 --- a/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs +++ b/Sources/Tests/UnitTests/Core/AnExactPowerTooLargeToBuildTest.cs @@ -17,27 +17,89 @@ 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. + /// holding it. Found by compilesweep, on (x^x - 1)^(e^(e^x)) at 6. Past about sixteen million bits + /// the power is left written, evaluated or simplified: it is finite and not zero, and as the decimal it was + /// +oo or 0, which made 2^(10^9) - 2^(10^9) NaN and 2^(10^9) > 2^(10^9 - 1) + /// False. What is decided of it is decided by structure, by its sign or by its exponent. + /// https://github.com/asc-community/AngouriMath/issues/1448 /// [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")] + [InlineData("46655^(10^20)")] + [InlineData("(-3)^(10^20 + 1)")] + [InlineData("(-3)^(10^20)")] + [InlineData("(5/2)^(10^7)")] + [InlineData("2^(10^9)")] + [InlineData("2^(-(10^9))")] + [InlineData("8^(10^9/3)")] + [InlineData("46655^(e^(e^6))")] + [InlineData("(1 + i)^(10^9)")] + public void IsLeftWritten(string input) + { + var evaluated = Within(() => input.ToEntity().Evaled, input); + Assert.True(evaluated is Powf, $"{input} was evaluated to {evaluated}"); + } + + [Theory] // 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")] + // What holds the power is settled where structure, sign or exponent settle it. + [InlineData("0 * 2^(10^9)", "0")] + [InlineData("2^(10^9) > 2^(10^9 - 1)", "true")] + [InlineData("2^(10^9) < 2^(10^9 - 1)", "false")] + [InlineData("(1/2)^(10^9) > (1/2)^(10^9 - 1)", "false")] + [InlineData("2^(-(10^9)) > 0", "true")] + [InlineData("2^(10^9) <= 0", "false")] + [InlineData("(-3)^(10^20 + 1) < 0", "true")] + [InlineData("(-3)^(10^20 + 1) >= 5", "false")] + [InlineData("2^(10^9) = 2^(10^9)", "true")] + [InlineData("2^(10^9) = 2^(10^9 - 1)", "false")] + [InlineData("2^(10^9) = 0", "false")] + // Otherwise by the logarithms of the magnitudes, where they differ by more than their error. + [InlineData("2^(10^9) > 5", "true")] + [InlineData("3^(10^9) > 2^(10^9)", "true")] + [InlineData("2^(10^9) > 10^(10^8)", "true")] + [InlineData("2^(-(10^9)) < 10^(-100)", "true")] + [InlineData("(-3)^(10^20 + 1) < -5", "true")] + [InlineData("(1 + 2^(-40))^(10^6) > 1", "true")] public void IsEvaluated(string input, string expected) => Assert.Equal(expected.ToEntity().Evaled, Within(() => input.ToEntity().Evaled, input)); + [Theory] + [InlineData("2^(10^9) - 2^(10^9)", "0")] + [InlineData("x - x + 2^(10^9) - 2^(10^9)", "0")] + [InlineData("2^(10^9) / 2^(10^9 - 1)", "2")] + [InlineData("2^(10^9) * 2^(-(10^9))", "1")] + [InlineData("2^(-(10^9)) > 0", "true")] + [InlineData("2^(10^9)", "2^(10^9)")] + public void IsSimplified(string input, string expected) + => Assert.Equal(expected.ToEntity().InnerSimplified, Within(() => input.ToEntity().Simplify(), input)); + + /// + /// Two magnitudes that may be equal are not told apart, and two that differ by less than any rounding are + /// not compared through it: as the decimals, the first was True only because both sides were +oo. + /// + [Theory] + [InlineData("2^(10^9) = 4^(5 * 10^8)")] + [InlineData("2^(10^9) > 2^(10^9) - 1")] + public void IsLeftUndecided(string input) + { + var evaluated = Within(() => input.ToEntity().Evaled, input); + Assert.True(evaluated is not Entity.Boolean, $"{input} was decided as {evaluated}"); + } + + /// A witness search that meets the power moves on, rather than hanging on it. + [Fact] + public void AQuantifierOverATowerReturns() + { + const string input = "forall n in ZZ* : 3^(n + 1) divides 2^(3^n) + 1"; + Assert.Equal(input.ToEntity(), Within(() => input.ToEntity().Evaled, input)); + } + [Theory] [InlineData("2^(10^10)")] [InlineData("46655^(10^20) + 1")]