From 2e6411b84587aba0f04f0227f46ed5d70542bd6f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 22 Oct 2026 17:08:15 +0000 Subject: [PATCH] Make a whole number the rational search finds an integer The continued fraction built its result with new Rational(...), past the check in Rational.Create that turns a denominator of one into an Integer, so a decimal just below a whole number came back as the Rational 1/1 or 3/1: not an Integer, and not equal to 1 or 3. Fixes #2069. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 ++++++++++++ .../Entity.Continuous.Rational.Definition.cs | 5 ++++- .../Tests/UnitTests/Core/NumericDowncasting.cs | 17 ++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 47f838101..fba254fc7 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -92,6 +92,18 @@ solver's check that a limit agrees from both sides goes through the same compari | `AreEqualNumerically("2 x (-1/4 + ln(x)/2) + 1/x * 2/4 * x^2", "x ln(x)")` | `false` | `true` | | `AreEqualNumerically` of `x ln(x)` and its antiderivative's derivative | `false` | `true` | +### A whole number the rational search finds is an integer + +**Silent: a wrong type and a wrong comparison.** The search that reads a number as the rational it is near built its +result without the check that turns a denominator of 1 into an integer. A value just below a whole number came back as +the `Rational` `1/1` or `3/1`, which is not an `Integer` and is not equal to `1` or `3` +([#2069](https://github.com/asc-community/AngouriMath/issues/2069)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `(Entity)0.9999999999999999999999999999m` | `Rational 1/1`, and `== 1` is `false` | `Integer 1` | +| `(Entity)2.9999999999999999999999999999m` | `Rational 3/1` | `Integer 3` | + ### A computed value is a rational only within half the working digits **Silent: wrong answers.** A number an evaluation produced became the small rational it was near within a fixed diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs index aaaf8c0c4..8ada2fd05 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs @@ -215,7 +215,10 @@ public static Rational Create(ERational value) var rat = FindRationalExactly(inv, iterCount - 1, tolerance); if (rat is null) return null; - return new Rational((intPart * sign + sign / rat.ERational).InLowestTerms()); + // Through Create, so that a whole number is an Integer: a value just below one is 0 + 1/1, and + // came back as the Rational 1/1, which is not equal to 1. + // https://github.com/asc-community/AngouriMath/issues/2069 + return Rational.Create(intPart * sign + sign / rat.ERational); } } diff --git a/Sources/Tests/UnitTests/Core/NumericDowncasting.cs b/Sources/Tests/UnitTests/Core/NumericDowncasting.cs index de1ddd3f0..aed399b0b 100644 --- a/Sources/Tests/UnitTests/Core/NumericDowncasting.cs +++ b/Sources/Tests/UnitTests/Core/NumericDowncasting.cs @@ -270,5 +270,20 @@ [Fact] public void FromRationalTestRealDowncastingEnabledInt() ); #endregion + + /// + /// A whole number the rational search finds is an : a value just below one came back + /// as the Rational 1/1, which is not equal to 1. + /// #2069 + /// + [Theory] + [InlineData("0.9999999999999999999999999999", 1)] + [InlineData("2.9999999999999999999999999999", 3)] + public void AWholeNumberFromTheRationalSearchIsAnInteger(string written, int whole) + { + Entity value = decimal.Parse(written, System.Globalization.CultureInfo.InvariantCulture); + value.Should().BeOfType(); + value.Should().Be((Entity)whole); + } } -} \ No newline at end of file +}