diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index f6dd61c5c..1f4f54946 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -192,6 +192,19 @@ integrals with `0` ([#1769](https://github.com/asc-community/AngouriMath/issues/
| `"e^(-160)".ToEntity().InnerSimplified` | `0` | `e ^ (-160)` |
| `"x^n*((1 - d^2)/x - x)^3*(1 + x^2 - d*x)/(x - d)/pi^2".ToEntity().Simplify()` | `0 provided not x - d = 0 and ...` | `x ^ n * ((1 - d ^ 2) / x - x) ^ 3 * (1 - d * x + x ^ 2) / (pi ^ 2 * (x - d))` |
+### The floor and the ceiling of a rational are exact whatever its size
+
+**Wrong answers fixed.** The floor and the ceiling of an exact rational went through a decimal at the
+working precision, and a rational with more digits than that was rounded onto the integer beside it
+before it was rounded down or up: `floor(10^120 - 1/3)` came out `10^120`. They are taken from the
+numerator and the denominator now, exactly
+([#1807](https://github.com/asc-community/AngouriMath/issues/1807)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"floor((3 * 10^120 - 1)/3)".ToEntity().InnerSimplified` | `10^120`, written out | `10^120 - 1` |
+| `"ceil((3 * 10^120 + 1)/3)".ToEntity().InnerSimplified` | `10^120`, written out | `10^120 + 1` |
+
### A rational function with symbols in it beside a root of a linear is split into partial fractions first
**Answers where there were none.** `1/(x (1 + x^2) sqrt(a + b x))` was declined, while
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 1fa881448..75855173a 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
@@ -706,7 +706,11 @@ protected override Entity InnerSimplify(bool isExact)
// both, and the exception was reaching the caller:
// https://github.com/asc-community/AngouriMath/issues/830
Real { IsFinite: false } n => n,
- Rational n => Integer.Create(n.EDecimal.Floor().ToEInteger()),
+ // From the numerator and the denominator: through EDecimal a rational of more
+ // digits than the precision was rounded onto the integer beside it, and
+ // floor(10^120 - 1/3) came out 10^120.
+ // https://github.com/asc-community/AngouriMath/issues/1807
+ Rational n => Integer.Create(RoundedQuotient(n.ERational, up: false)),
Real n when !isExact => Integer.Create(n.EDecimal.Floor().ToEInteger()),
Complex n when !isExact => Complex.Create(
n.RealPart.EDecimal.Floor(), n.ImaginaryPart.EDecimal.Floor()),
@@ -720,6 +724,21 @@ protected override Entity InnerSimplify(bool isExact)
(@this, a) => ((Floorf)@this).New(a), isExact);
}
+ ///
+ /// The floor, or with the ceiling, of a rational, exactly: the quotient of
+ /// its numerator by its denominator, which is positive, rounded toward zero by the division and
+ /// moved one step where that went the wrong way.
+ ///
+ private static PeterO.Numbers.EInteger RoundedQuotient(PeterO.Numbers.ERational value, bool up)
+ {
+ var division = value.Numerator.DivRem(value.Denominator);
+ var (quotient, remainder) = (division[0], division[1]);
+ if (remainder.IsZero)
+ return quotient;
+ return up ? (value.Numerator.Sign > 0 ? quotient + PeterO.Numbers.EInteger.One : quotient)
+ : (value.Numerator.Sign < 0 ? quotient - PeterO.Numbers.EInteger.One : quotient);
+ }
+
public partial record Ceilf
{
// Defined everywhere in the complex plane, taken componentwise.
@@ -733,7 +752,7 @@ protected override Entity InnerSimplify(bool isExact)
Integer n => n,
// As in Floorf: https://github.com/asc-community/AngouriMath/issues/830
Real { IsFinite: false } n => n,
- Rational n => Integer.Create(n.EDecimal.Ceiling().ToEInteger()),
+ Rational n => Integer.Create(RoundedQuotient(n.ERational, up: true)),
Real n when !isExact => Integer.Create(n.EDecimal.Ceiling().ToEInteger()),
Complex n when !isExact => Complex.Create(
n.RealPart.EDecimal.Ceiling(), n.ImaginaryPart.EDecimal.Ceiling()),
diff --git a/Sources/Tests/UnitTests/Convenience/FloorCeilTest.cs b/Sources/Tests/UnitTests/Convenience/FloorCeilTest.cs
index 1f2976503..dc56c0a89 100644
--- a/Sources/Tests/UnitTests/Convenience/FloorCeilTest.cs
+++ b/Sources/Tests/UnitTests/Convenience/FloorCeilTest.cs
@@ -228,5 +228,26 @@ public void TheLimitAwayFromAJumpIsTheValue(string input, string destination, st
public void TheLimitOnAJumpIsDeclined(string input, string destination)
=> Assert.IsType(
input.ToEntity().Limit("x", destination.ToEntity()));
+
+ ///
+ /// A rational with more digits than the precision, a third away from an integer on either
+ /// side, rounded exactly: through EDecimal it was rounded onto the integer first, and
+ /// floor(10^120 - 1/3) came out 10^120.
+ /// #1807
+ ///
+ [Theory]
+ [InlineData("floor((3 * 10^120 - 1)/3)", "10^120 - 1")]
+ [InlineData("floor((3 * 10^120 + 1)/3)", "10^120")]
+ [InlineData("ceil((3 * 10^120 - 1)/3)", "10^120")]
+ [InlineData("ceil((3 * 10^120 + 1)/3)", "10^120 + 1")]
+ [InlineData("floor(-(3 * 10^400 - 1)/3)", "-10^400")]
+ [InlineData("floor(-(3 * 10^400 + 1)/3)", "-10^400 - 1")]
+ [InlineData("ceil(-(3 * 10^400 - 1)/3)", "-10^400 + 1")]
+ [InlineData("ceil(-(3 * 10^400 + 1)/3)", "-10^400")]
+ public void ARationalOfManyDigitsIsRoundedExactly(string input, string expected)
+ {
+ Assert.Equal(expected.ToEntity().InnerSimplified, input.ToEntity().InnerSimplified);
+ Assert.Equal(expected.ToEntity().InnerSimplified, input.ToEntity().Evaled);
+ }
}
}