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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,20 @@ powers of `x` found it.
|---|---|---|
| `"(3x - 4) * sqrt(x) + 3 + 3x - 4x^2 = 0".Solve("x")` | four roots, one of them 1.3233…, which is not a root | the other three |

### A step function that has a limit from one side is that limit on that side

**Silent: a limit of a power whose base is a `floor`, `ceil`, `round` or `sgn` that is 0 on the side approached is 0,
where it was 1.** These functions take only whole values, so where one has a limit from a side it equals that limit on
a whole interval on that side, and is now put in as it before the limit is taken. `floor(x)` is 0 on `(0, 1)`, so
`floor(x)^x` there is 0 to a positive power; it was read as the form `0^0` through `e^(x ln(floor(x)))` and answered 1.
Two-sided, each side is now taken with its own value, and `floor(x)^x` at 0 is 1 from the left and 0 from the right.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"floor(x)^x".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `1` | `0` |
| `"floor(x)^tanh(x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | unevaluated | `0` |
| `"floor(x)^x".ToEntity().Limit("x", 0)` | unevaluated | `NaN`, the sides differ |

### A limit of a logarithm that is not decided is left unevaluated

**Not silent: what overflowed the stack now returns.** A limit of a logarithm whose argument has no limit, as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ internal static partial class LimitFunctional
return null;
}

/// <summary>
/// <paramref name="expr"/> with each <c>floor</c>, <c>ceil</c>, <c>round</c> and <c>sgn</c> of
/// <paramref name="x"/> whose limit from <paramref name="side"/> is a whole number put
/// in as that number. Each takes only whole values, so where it has a limit it takes
/// that value on the whole of some interval on that side: the substitution is exact
/// there, and not an approximation. Without it, <c>floor(x) ^ x</c> at 0+ was read as
/// the form 0^0 and answered 1 through <c>e ^ (x ln(floor(x)))</c>, where on (0, 1) it
/// is 0 to a positive power, which is 0.
/// </summary>
private static Entity WithStepsAtTheirLimits(Entity expr, Variable x, Entity dest, ApproachFrom side)
{
if (!expr.Nodes.Any(node => node is Floorf or Ceilf or Roundf or Signumf))
return expr;
return expr.Replace(node =>
node is Floorf or Ceilf or Roundf or Signumf
&& node.ContainsNode(x)
&& node.ComputeLimitDivideEtImpera(x, dest, side) is { } limit
&& limit.Evaled is Integer whole
? whole
: node);
}

/// <summary>
/// The limit where the two one-sided limits agree, asked of each side through the
/// whole of <see cref="ComputeLimit"/>, or <see langword="null"/> where either side
Expand Down Expand Up @@ -278,6 +300,7 @@ internal static T WithinABudget<T>(int limits, System.Func<T> compute)

if (side is ApproachFrom.Left or ApproachFrom.Right)
{
expr = WithStepsAtTheirLimits(expr, x, dest, side);
var oneSided = expr.ComputeLimitDivideEtImpera(x, dest, side);
if (oneSided is { } found && (acceptNaN || found.Evaled != MathS.NaN))
return oneSided;
Expand Down Expand Up @@ -377,8 +400,10 @@ internal static T WithinABudget<T>(int limits, System.Func<T> compute)
// https://github.com/asc-community/AngouriMath/issues/1186
return atInfinity;
}
else if (expr.ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Left) is { } fromLeft
&& expr.ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Right) is { } fromRight)
// Each side with its own steps put in: floor(x) ^ x at 0 is 1 from the left and 0
// from the right, and read bare both sides were 1.
else if (WithStepsAtTheirLimits(expr, x, dest, ApproachFrom.Left).ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Left) is { } fromLeft
&& WithStepsAtTheirLimits(expr, x, dest, ApproachFrom.Right).ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Right) is { } fromRight)
{
if (fromLeft == fromRight && (acceptNaN || fromLeft.Evaled != MathS.NaN))
return fromLeft;
Expand Down
42 changes: 42 additions & 0 deletions Sources/Tests/UnitTests/Calculus/AStepFunctionNearItsLimitTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// 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 AngouriMath;
using AngouriMath.Core;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// <c>floor</c>, <c>ceil</c>, <c>round</c> and <c>sgn</c> take only whole values, so one that has a limit from a side is
/// equal to it on an interval on that side. <c>floor(x) ^ x</c> at 0+ is 0 to a positive power on <c>(0, 1)</c>, and was
/// read as the form <c>0^0</c> and answered 1. No issue was filed; found by work/limitcheck at seed 11.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AStepFunctionNearItsLimitTest
{
[Theory]
[InlineData("floor(x)^x", "0")]
[InlineData("floor(x)^tanh(x)", "0")]
[InlineData("floor(x)^sin(x)", "0")]
[InlineData("ceil(x)^x", "1")]
[InlineData("x^sgn(x)", "0")]
public void FromTheRight(string expression, string limit)
=> Assert.Equal(limit.ToEntity(), expression.ToEntity().Limit("x", 0, ApproachFrom.Right).Simplify());

// floor(x) is -1 on (-1, 0), and (-1)^x tends to 1.
[Fact]
public void FromTheLeft()
=> Assert.Equal(1, "floor(x)^x".ToEntity().Limit("x", 0, ApproachFrom.Left).Simplify());

// 1 from the left and 0 from the right: no limit.
[Fact]
public void BothSidesDiffer()
=> Assert.Equal(MathS.NaN, "floor(x)^x".ToEntity().Limit("x", 0).Simplify());
}
}
Loading