From 97faa629198f10145416cf4c219bcf5a1a5434af Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 15:50:48 +0000 Subject: [PATCH 1/2] A logarithm to base 0 has no value log(0, 2) evaluated to 0, and log(x, 2) compiled to (-0, -0) at 0. Both took it as ln(x) / ln(0), which is ln(x) / (-oo). But 0 is not a base, since no power of it is anything but 0, and Logf's condition already excluded it: not x = 0 and not x = 1. Number.Log, ComplexBranches.Log and the LINQ compiler's double and float Log now answer an exact zero base with NaN. Over double, Math.Log already gave NaN, except for an antilogarithm of 1, where it gave -0. A base that is small but not 0 keeps its logarithm, so the test is for an exact zero, not IsZero's tolerance. LogarithmToBaseZeroTest takes six logarithms to base 0, evaluated and simplified, and four compiled by FastExpression, the LINQ compiler over Complex and over double. Seven fail on master. Three logarithms to other bases pin what has not changed, 10^(-100) among them. Found by work/domaincheck. Fixes #2255. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 12 ++++ Sources/.editorconfig | 3 + .../Entity/Continuous/Number/Operators.cs | 5 ++ .../Functions/Compilation/ComplexBranches.cs | 6 +- .../Compilation/IntoLinq/MathAllMethods.cs | 5 +- .../Compilation/IntoLinq/MathAllMethods.tt | 5 +- .../Convenience/LogarithmToBaseZeroTest.cs | 62 +++++++++++++++++++ 7 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 59064adbc..648968336 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -602,6 +602,18 @@ 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 +([#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)` | + ### `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/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs b/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs new file mode 100644 index 000000000..8c46b6a4f --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs @@ -0,0 +1,62 @@ +// +// 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"); + } + + /// 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}"); + } + } +} From e3ffadc65595c2cce23e4049d6193702c7299ba9 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 16:12:26 +0000 Subject: [PATCH 2/2] The logarithm of 1 excludes the base 0 log(b, 1) simplified to 0 provided not b = 1, which is 0 at b = 0. With log(0, 1) now NaN, BoundCheck found that disagreement, and the fold now provides not b = 0 as well, as log's condition does. TheLogarithmOfOneExcludesTheBase0 takes the simplification at 0, 1 and 2. BoundCheck has 0 disagreements again. Part of #2255. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 4 +++- .../Evaluation.Continuous.Arithmetics.Classes.cs | 10 ++++++---- .../Convenience/LogarithmToBaseZeroTest.cs | 13 +++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 648968336..3d02d714f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -606,13 +606,15 @@ reals, to `1` and `0` ([#2195](https://github.com/asc-community/AngouriMath/issu **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 +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 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 index 8c46b6a4f..20fe97acc 100644 --- a/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs +++ b/Sources/Tests/UnitTests/Convenience/LogarithmToBaseZeroTest.cs @@ -48,6 +48,19 @@ public void CompiledItHasNoValue(double antilogarithm) 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)]