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 @@ -602,6 +602,20 @@ reals, to `1` and `0` ([#2195](https://github.com/asc-community/AngouriMath/issu
| `"arg(x)".ToEntity().Compile("x").Call(-2)` | throws `UncompilableNodeException`, `arg` being a variable | `pi` |
| `"conj(x)".ToEntity().Compile<Complex, Complex>("x")(3 + 4i)` | throws `UncompilableNodeException`, `conj` being a variable | `3 - 4i` |

### A logarithm to base 0 has no value

**Silent: `log(0, x)` is `NaN`, where it was `0` for a positive `x`.** Evaluation and both compilers took it as
`ln(x) / ln(0)`, which is `ln(x) / (-oo)`, and came out with 0. But 0 is not a base, since no power of it is anything
but 0, and `log`'s own condition already excluded it. A base that is small but not 0 keeps its logarithm.
`log(b, 1)`, simplified to 0 for every base but 1, now excludes the base 0 as well
([#2255](https://github.com/asc-community/AngouriMath/issues/2255)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"log(0, 2)".ToEntity().Evaled` | `0` | `NaN` |
| `"log(x, 2)".ToEntity().Compile("x").Call(0)` | `(-0, -0)` | `(NaN, 0)` |
| `"log(x, 1)".ToEntity().Simplify()` | `0 provided not x = 1` | `0 provided not x = 0 and not x = 1` |

### `re` and `im` are the real and the imaginary part

**Not silent: `re(…)` and `im(…)`, and `real(…)` and `imag(…)`, are now the real and the imaginary part, where they were
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Common/FiniteSetStatementCompilationTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Convenience/EulerGammaTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,11 @@ static Complex BinaryIntPow(Complex num, EInteger val)
/// <param name="x">The number of which we want to get its base power</param>
public static Complex Log(Complex @base, Complex x)
{
// 0 is not a base: no power of it is anything but 0, and log_0(x) has no value, which is what
// Logf's condition says. ln(x)/ln(0) is ln(x)/(-oo), and was 0. An exact zero only: a base that
// is small and not 0 has its logarithm. https://github.com/asc-community/AngouriMath/issues/2255
if (@base.RealPart.EDecimal.IsZero && @base.ImaginaryPart.EDecimal.IsZero)
return Real.NaN;
if (LostToExponentRange(x) || LostToExponentRange(@base))
return Ln(x) / Ln(@base);
// A base of 1 is excluded from the real shortcut so that it falls through to the
Expand Down
6 changes: 5 additions & 1 deletion Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ public static NumericsComplex Arcsin(NumericsComplex a)
/// https://github.com/asc-community/AngouriMath/issues/2239
/// </summary>
public static NumericsComplex Log(NumericsComplex @base, NumericsComplex value)
=> RealProducts.Quotient(NumericsComplex.Log(Unsigned(value)), NumericsComplex.Log(Unsigned(@base)));
=> @base == NumericsComplex.Zero
// A logarithm to base 0 has no value, as in evaluation, where ln(value) / ln(0) made it 0.
// https://github.com/asc-community/AngouriMath/issues/2255
? new NumericsComplex(double.NaN, 0)
: RealProducts.Quotient(NumericsComplex.Log(Unsigned(value)), NumericsComplex.Log(Unsigned(@base)));

public static NumericsComplex Arccos(NumericsComplex a) => NumericsComplex.Acos(Unsigned(a));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ internal static class MathAllMethods

public static System.Numerics.Complex Log(System.Numerics.Complex a, System.Numerics.Complex b)
=> AngouriMath.Core.Compilation.ComplexBranches.Log(a, b);
// A logarithm to base 0 has no value. https://github.com/asc-community/AngouriMath/issues/2255
public static double Log(double a, double b)
=> Math.Log(b, a);
=> a == 0 ? double.NaN : Math.Log(b, a);
public static float Log(float a, float b)
=> (float)Math.Log(b, a);
=> a == 0 ? float.NaN : (float)Math.Log(b, a);
public static long Log(long a, long b)
=> (long)Math.Log(b, a);
public static int Log(int a, int b)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ namespace AngouriMath.Core.Compilation.IntoLinq

public static System.Numerics.Complex Log(System.Numerics.Complex a, System.Numerics.Complex b)
=> AngouriMath.Core.Compilation.ComplexBranches.Log(a, b);
// A logarithm to base 0 has no value. https://github.com/asc-community/AngouriMath/issues/2255
public static double Log(double a, double b)
=> Math.Log(b, a);
=> a == 0 ? double.NaN : Math.Log(b, a);
public static float Log(float a, float b)
=> (float)Math.Log(b, a);
=> a == 0 ? float.NaN : (float)Math.Log(b, a);
public static long Log(long a, long b)
=> (long)Math.Log(b, a);
public static int Log(int a, int b)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,11 +463,13 @@ when a.Evaled is Real below && below > 0 && below < 1
=> Real.PositiveInfinity.Provided(condition),

// log_b(1) is 0/ln(b), which is 0 for every base but 1, where it is
// 0/0 -- and every division by zero here is NaN. The condition belongs
// on the answer rather than gating the rule, because at b = 1 the
// expression is genuinely undefined and not merely something else.
// 0/0 -- and every division by zero here is NaN -- and 0, which is not a
// base (https://github.com/asc-community/AngouriMath/issues/2255). The
// condition belongs on the answer rather than gating the rule, because at
// those bases the expression is genuinely undefined and not merely
// something else.
({ DomainCondition: var condition }, Integer(1))
=> Integer.Zero.Provided(condition).Provided(!a.EqualTo(1)),
=> Integer.Zero.Provided(condition).Provided(!a.EqualTo(0) & !a.EqualTo(1)),

_ => null
},
Expand Down
75 changes: 75 additions & 0 deletions Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// 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.Numerics;
using AngouriMath;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Convenience
{
/// <summary>
/// A logarithm to base 0 has no value, as <c>log</c>'s condition says, where <c>ln(x) / ln(0)</c>, which is
/// <c>ln(x) / (-oo)</c>, made it 0 in evaluation and in the compilers.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2255">#2255</a>
/// </summary>
[Trait("Area", "Convenience")]
public sealed class LogarithmToBaseZeroTest
{
[Theory]
[InlineData("log(0, 2)")]
[InlineData("log(0, 1/2)")]
[InlineData("log(0, -3)")]
[InlineData("log(0, i)")]
[InlineData("log(0, 0)")]
[InlineData("log(0, 1)")]
public void ItHasNoValue(string expression)
{
Assert.True(expression.ToEntity().Evaled.IsNaN, $"{expression} evaluates to {expression.ToEntity().Evaled}");
Assert.True(expression.ToEntity().Simplify().IsNaN, $"{expression} simplifies to {expression.ToEntity().Simplify()}");
}

[Theory]
[InlineData(2)]
[InlineData(0.5)]
[InlineData(-3)]
[InlineData(1)]
public void CompiledItHasNoValue(double antilogarithm)
{
var expression = $"log(x, {antilogarithm})".ToEntity();
var fast = expression.Compile("x").Call(0);
var linq = expression.Compile<Complex, Complex>("x")(0);
Assert.True(double.IsNaN(fast.Real) || double.IsNaN(fast.Imaginary), $"{expression} at 0: {fast} by FastExpression");
Assert.True(double.IsNaN(linq.Real) || double.IsNaN(linq.Imaginary), $"{expression} at 0: {linq} by LINQ");
Assert.True(double.IsNaN(expression.Compile<double, double>("x")(0)), $"{expression} at 0 over double");
}

/// <summary>
/// <c>log(x, 1)</c> is 0 for every base but 0 and 1, and its simplification says so: it said only <c>not x = 1</c>,
/// and was 0 at <c>x = 0</c>.
/// </summary>
[Fact]
public void TheLogarithmOfOneExcludesTheBase0()
{
var simplified = "log(x, 1)".ToEntity().Simplify();
Assert.True(simplified.Substitute("x", 0).Evaled.IsNaN, $"{simplified} at 0");
Assert.True(simplified.Substitute("x", 1).Evaled.IsNaN, $"{simplified} at 1");
Assert.Equal(Entity.Number.Integer.Zero, simplified.Substitute("x", 2).Evaled);
}

/// <summary>A base that is small and not 0 has its logarithm.</summary>
[Theory]
[InlineData("log(2, 8)", 3)]
[InlineData("log(1/2, 4)", -2)]
[InlineData("log(10^(-100), 10^(-200))", 2)]
public void AnotherBaseHasItsLogarithm(string expression, double value)
{
var evaluated = ((Entity.Number.Complex)expression.ToEntity().Evaled).ToNumerics();
Assert.True(Complex.Abs(evaluated - value) < 1e-12, $"{expression} evaluates to {evaluated}");
}
}
}
Loading