From 8059e73ae0f0fbfa428eea88b21330737534d18c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 21:04:26 +0000 Subject: [PATCH] An exact rational is zero, positive or negative as it is 0 / 10^(-200) was NaN, where it is 0, and so was 0 ^ (1/10^250). Complex.IsZero and Real.IsPositive and IsNegative read the decimal, and a rational's decimal is written out in the precision context, whose exponent range writes every rational under about 1e-199 in size as 0. So the divisor was taken for zero, and the exponent for not positive. That is what made #2270's reduced row echelon form NaN, dividing 0 by a denominator near 1e-246. A rational part is now read by its ERational, which it holds; its decimal is written out only when asked for. ATinyRationalIsNotZeroTest takes five quotients and powers of tiny rationals, three that have not changed, and the sign and the zero test of 1/10^250 and its negation; six fail on master. The suite, the trio, F#, AOT and the eight harnesses pass. Diagnosed by the subagent that traced #2270. Fixes #2277. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 14 +++++ Sources/.editorconfig | 3 ++ .../Entity.Continuous.Complex.Definition.cs | 10 +++- .../Entity.Continuous.Real.Definition.cs | 11 +++- .../Core/ATinyRationalIsNotZeroTest.cs | 51 +++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Core/ATinyRationalIsNotZeroTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 4958f4e35..a065338c4 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -152,6 +152,20 @@ arcsine of that is `NaN`. Of `arcsin(k + c)` for `k` from 1 to 40 and `c` in `pi | `"arcsin(6 + pi)".ToEntity().Evaled` | `NaN` | `1.5707963267948966 - 2.902976737696685i`, to 15 digits | | `"arccos(6 + pi)".ToEntity().Evaled` | `NaN` | `2.902976737696685i`, to 15 digits | +### An exact rational is zero, positive or negative as it is + +**Silent: 0 divided by a rational smaller than about 1e-199 is 0, where it was NaN.** Whether an exact rational was zero, +and its sign, was read off its decimal, written out in the precision context, whose exponent range writes every rational +under about 1e-199 in size as 0. So `0 / 10^(-200)` was a division by zero, and `0 ^ (1/10^250)` a power to an exponent +that was not positive. A rational is now read by its exact value +([#2277](https://github.com/asc-community/AngouriMath/issues/2277)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"0 / 10^(-200)".ToEntity().Evaled` | `NaN` | `0` | +| `"0 / (3/10^260)".ToEntity().Evaled` | `NaN` | `0` | +| `"0 ^ (1/10^250)".ToEntity().Evaled` | `NaN` | `0` | + ### A system with a product equation is solved branch by branch **Silent: solutions that were missing are answered.** `EquationSystem.Solve` eliminated `x * y = 0` for `y` as `0`, diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 96598d54f..77b10a26d 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -66,6 +66,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Core/DividesTest.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/Core/ATinyRationalIsNotZeroTest.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/Core/CardinalityTest.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/Entity.Continuous.Complex.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs index 829f33a64..b0d9b6c8f 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs @@ -74,7 +74,15 @@ private protected Complex(Real? real, Real? imaginary) => /// /// Checks if both parts equal 0 /// - public new bool IsZero => RealPart.EDecimal.IsZero && ImaginaryPart.EDecimal.IsZero; + /// + /// A rational part is zero when it is, and not when its decimal is: written out in the + /// precision context, every rational under 1e-199 in size is 0, and 0 / 10^(-250) was NaN. + /// #2277 + /// + public new bool IsZero => PartIsZero(RealPart) && PartIsZero(ImaginaryPart); + + private static bool PartIsZero(Real part) => + part is Rational rational ? rational.ERational.IsZero : part.EDecimal.IsZero; /// /// Creates an instance of Complex diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs index c723d7a23..e89787d8f 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs @@ -49,10 +49,17 @@ private protected Real() : base(null, null) { } public override bool IsExact => !EDecimal.IsFinite; /// Strictly less than 0 - public bool IsNegative => EDecimal.IsNegative; + /// + /// A rational's sign is its own, and not its decimal's: the decimal is written out in the + /// precision context, whose exponent range writes every rational under 1e-199 in size as + /// zero, so 0^(10^(-250)) was NaN. + /// #2277 + /// + public bool IsNegative => this is Rational rational ? rational.ERational.Sign < 0 : EDecimal.IsNegative; /// Strictly greater than 0 - public bool IsPositive => !EDecimal.IsNegative && !EDecimal.IsZero; + /// A rational's sign is its own, as for . + public bool IsPositive => this is Rational rational ? rational.ERational.Sign > 0 : !EDecimal.IsNegative && !EDecimal.IsZero; /// /// Creates an instance of Real diff --git a/Sources/Tests/UnitTests/Core/ATinyRationalIsNotZeroTest.cs b/Sources/Tests/UnitTests/Core/ATinyRationalIsNotZeroTest.cs new file mode 100644 index 000000000..3b99bdb92 --- /dev/null +++ b/Sources/Tests/UnitTests/Core/ATinyRationalIsNotZeroTest.cs @@ -0,0 +1,51 @@ +// +// 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.Extensions; +using Xunit; +using static AngouriMath.Entity.Number; + +namespace AngouriMath.Tests.Core +{ + /// + /// An exact rational is zero, positive or negative as it is, and not as its decimal is: the decimal is written out in the + /// precision context, whose exponent range writes every rational under 1e-199 in size as 0, so 0 / 10^(-200) was NaN. + /// #2277 + /// + [Trait("Area", "Core")] + public sealed class ATinyRationalIsNotZeroTest + { + [Theory] + [InlineData("0 / 10^(-200)", "0")] + [InlineData("0 / 10^(-250)", "0")] + [InlineData("0 / (3/10^260)", "0")] + [InlineData("0 / (-1/10^300)", "0")] + [InlineData("0 ^ (1/10^250)", "0")] + [InlineData("0 / 10^(-199)", "0")] + [InlineData("10^(-250) * 10^250", "1")] + [InlineData("(1/10^250) / (1/10^250)", "1")] + public void ItIsWhatItIs(string expression, string value) + { + Assert.Equal(value.ToEntity(), expression.ToEntity().Evaled); + Assert.Equal(value.ToEntity(), expression.ToEntity().Simplify()); + } + + [Fact] + public void ItsSignIsItsOwn() + { + var tiny = Rational.Create(1, PeterO.Numbers.EInteger.FromInt32(10).Pow(250)); + Assert.False(tiny.IsZero); + Assert.True(tiny.IsPositive); + Assert.False(tiny.IsNegative); + var negative = (Rational)(-tiny); + Assert.True(negative.IsNegative); + Assert.False(negative.IsPositive); + Assert.True(Integer.Zero.IsZero); + Assert.False(Integer.Zero.IsPositive || Integer.Zero.IsNegative); + } + } +}