diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dabd21a22..b1463c12b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -262,6 +262,19 @@ its rows in the order it finds them, which can differ from the eliminator's | `MathS.Equations("x*y", "x + y^2 - 1").Solve("x", "y")` | `[[1, 0]]` | `[[1, 0], [0, 1], [0, -1]]` | | `MathS.Equations("x*y + 3*x - 5*y - 15", "x^2 - 3*x - 4*y - 10").Solve("x", "y")` | `[[1, -3], [2, -3]]` | `[[5, 0], [1, -3], [2, -3]]` | +### `round` is exact, and an infinity is its own nearest integer + +**Silent for a long rational, not silent for an infinity.** `round` of a rational with more digits than the precision was +taken through its decimal, which had already rounded it onto the integer beside it, and `round` of an infinity threw +`OverflowException`. `floor` and `ceil` were fixed for both before; `round` now does as they do +([#2282](https://github.com/asc-community/AngouriMath/issues/2282)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"round(10^120 + 2/3)".ToEntity().Evaled` | `10^120` | `10^120 + 1` | +| `"round(10^120 + 3/2)".ToEntity().Evaled` | `10^120` | `10^120 + 2` | +| `"round(+oo)".ToEntity().Evaled` | throws `OverflowException` | `+oo` | + ### A squared sine and cosine with one coefficient are found anywhere in a sum **Silent: an identity reaches its value however the sum is written.** `k sin(x)^2 + k cos(x)^2` was reduced to `k` diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 96e41cc4c..c16a34f24 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -57,6 +57,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/SpellingsAtRiskTest.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/RoundIsExactTest.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/FactorialCacheIsThreadSafeTest.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/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs index e20ff4220..278da84fc 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 @@ -1291,6 +1291,19 @@ private static PeterO.Numbers.EInteger RoundedQuotient(PeterO.Numbers.ERational : (value.Numerator.Sign < 0 ? quotient - PeterO.Numbers.EInteger.One : quotient); } + /// + /// The integer nearest to a rational, exactly, and the even one of the two at a tie: its floor, or + /// the integer above where the rational is more than a half above it, or a half above it and the + /// floor is odd. + /// + private static PeterO.Numbers.EInteger RoundedHalfEven(PeterO.Numbers.ERational value) + { + var floor = RoundedQuotient(value, up: false); + var aboveTheFloor = value.Subtract(PeterO.Numbers.ERational.FromEInteger(floor)) + .CompareTo(PeterO.Numbers.ERational.Create(1, 2)); + return aboveTheFloor > 0 || aboveTheFloor == 0 && !floor.IsEven ? floor + PeterO.Numbers.EInteger.One : floor; + } + public partial record Ceilf { // Defined everywhere in the complex plane, taken componentwise. @@ -1325,7 +1338,13 @@ protected override Entity InnerSimplify(bool isExact) a => a switch { Integer n => n, - Rational n => Integer.Create(n.EDecimal.RoundHalfEven().ToEInteger()), + // As in Floorf: an infinity is its own nearest integer and NaN propagates, + // where EInteger refused both and round(+oo) threw, and a rational is + // rounded from its numerator and denominator, where through EDecimal + // round(10^120 + 2/3) came out 10^120. + // https://github.com/asc-community/AngouriMath/issues/2282 + Real { IsFinite: false } n => n, + Rational n => Integer.Create(RoundedHalfEven(n.ERational)), Real n when !isExact => Integer.Create(n.EDecimal.RoundHalfEven().ToEInteger()), Complex n when !isExact => Complex.Create( n.RealPart.EDecimal.RoundHalfEven(), n.ImaginaryPart.EDecimal.RoundHalfEven()), diff --git a/Sources/Tests/UnitTests/Convenience/RoundIsExactTest.cs b/Sources/Tests/UnitTests/Convenience/RoundIsExactTest.cs new file mode 100644 index 000000000..e03bf6a32 --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/RoundIsExactTest.cs @@ -0,0 +1,43 @@ +// +// 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; + +namespace AngouriMath.Tests.Convenience +{ + /// + /// round of a rational is the nearest integer, exactly, and the even one at a tie, and an infinity is its own: through + /// EDecimal a rational of more digits than the precision was rounded onto the integer beside it first, and + /// round(10^120 + 2/3) came out 10^120, while round(+oo) threw OverflowException. floor and ceil had both defects and + /// were fixed in #830 and #1807. + /// #2282 + /// + [Trait("Area", "Convenience")] + public sealed class RoundIsExactTest + { + [Theory] + [InlineData("round(10^120 + 2/3)", "10^120 + 1")] + [InlineData("round(10^120 - 2/3)", "10^120 - 1")] + [InlineData("round(10^120 + 3/2)", "10^120 + 2")] + [InlineData("round(10^120 + 1/2)", "10^120")] + [InlineData("round(-10^120 - 2/3)", "-10^120 - 1")] + [InlineData("round(-10^400 + 1/3)", "-10^400")] + [InlineData("round(2/3)", "1")] + [InlineData("round(5/2)", "2")] + [InlineData("round(7/2)", "4")] + [InlineData("round(-5/2)", "-2")] + [InlineData("round(-7/2)", "-4")] + [InlineData("round(+oo)", "+oo")] + [InlineData("round(-oo)", "-oo")] + public void ItIsTheNearestInteger(string input, string expected) + { + Assert.Equal(expected.ToEntity().InnerSimplified, input.ToEntity().InnerSimplified); + Assert.Equal(expected.ToEntity().InnerSimplified, input.ToEntity().Evaled); + } + } +}