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