From 221a2cccc021bab73fe75ed5d14072c25ca5c135 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 6 Oct 2026 14:42:52 +0000 Subject: [PATCH] The floor and the ceiling of a rational are exact whatever its size Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 +++++++++++ ...aluation.Continuous.Arithmetics.Classes.cs | 23 +++++++++++++++++-- .../UnitTests/Convenience/FloorCeilTest.cs | 21 +++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) 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); + } } }