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); + } + } +}