diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 59064adbc..3d02d714f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -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("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 diff --git a/Sources/.editorconfig b/Sources/.editorconfig index b677d246c..9ddb5d3ef 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -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 diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs index 34eb8e533..bb7382737 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs @@ -695,6 +695,11 @@ static Complex BinaryIntPow(Complex num, EInteger val) /// The number of which we want to get its base power 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 diff --git a/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs b/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs index 6b7f6e5c0..6677d5d4d 100644 --- a/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs +++ b/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs @@ -59,7 +59,11 @@ public static NumericsComplex Arcsin(NumericsComplex a) /// https://github.com/asc-community/AngouriMath/issues/2239 /// 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)); diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs index eae82e858..35ec2fe5c 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs @@ -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) diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt index a7c0117d5..2813074d1 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt @@ -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) 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 f8a8bc6fd..12a0a35d6 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 @@ -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 }, diff --git a/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs b/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs new file mode 100644 index 000000000..20fe97acc --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs @@ -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 +{ + /// + /// A logarithm to base 0 has no value, as log's condition says, where ln(x) / ln(0), which is + /// ln(x) / (-oo), made it 0 in evaluation and in the compilers. + /// #2255 + /// + [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("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("x")(0)), $"{expression} at 0 over double"); + } + + /// + /// log(x, 1) is 0 for every base but 0 and 1, and its simplification says so: it said only not x = 1, + /// and was 0 at x = 0. + /// + [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); + } + + /// A base that is small and not 0 has its logarithm. + [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}"); + } + } +}