From ba7acfe92e8a0261f7517593ae04c7fd011c0cd3 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 13 Oct 2026 05:57:22 +0000 Subject: [PATCH 1/2] A rational writes its decimal out again in a precision other than the one it kept Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Entity.Continuous.Rational.Definition.cs | 45 ++++++++++++++++--- .../Core/HighPrecisionFunctionsTest.cs | 21 +++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) 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 6bdc30ed7..1afff767d 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rational.Definition.cs @@ -30,14 +30,45 @@ public partial record Rational : Real, System.IComparable private protected Rational(ERational value) => ERational = value; /// - /// The value written out in the decimal precision of the moment it is first - /// asked for, not of the moment the rational was made: an integer's exact - /// form is the , and writing a hundred digits out for - /// every integer and fraction the simplifier makes was a third of the cost of - /// making one (https://github.com/asc-community/AngouriMath/issues/1338). + /// The value written out in the decimal precision of the moment it is asked for, + /// not of the moment the rational was made: an integer's exact form is the + /// , and writing a hundred digits out for every integer + /// and fraction the simplifier makes was a third of the cost of making one + /// (https://github.com/asc-community/AngouriMath/issues/1338). Kept with the + /// context it was written in and written again in another: kept alone, a + /// rational first asked at the default precision answered a hundred digits + /// under a context of three hundred, and so did every value computed from it + /// (https://github.com/asc-community/AngouriMath/issues/1868). /// - public override EDecimal EDecimal => edecimal.GetValue(static @this => @this.ERational.ToEDecimal(MathS.Settings.DecimalPrecisionContext), this); - private LazyPropertyA edecimal; + public override EDecimal EDecimal + { + get + { + EContext context = MathS.Settings.DecimalPrecisionContext; + if (writtenOut.In is { } held && ReferenceEquals(held.Context, context)) + return held.Value; + var value = ERational.ToEDecimal(context); + writtenOut.In = new WrittenOut(context, value); + return value; + } + } + private WrittenOutCache writtenOut; + + /// One writing out and its context, replaced whole so that a reader never sees half of one. + private sealed record WrittenOut(EContext Context, EDecimal Value); + + /// + /// Behind a struct equal to every other, as the caches on are: + /// a record compares every field, and two equal rationals are equal whichever of + /// them has been written out. + /// + private struct WrittenOutCache : IEquatable + { + internal WrittenOut? In; + public bool Equals(WrittenOutCache other) => true; + public override bool Equals(object? obj) => obj is WrittenOutCache; + public override int GetHashCode() => 0; + } internal override Priority Priority => Priority.Div; diff --git a/Sources/Tests/UnitTests/Core/HighPrecisionFunctionsTest.cs b/Sources/Tests/UnitTests/Core/HighPrecisionFunctionsTest.cs index 308c03ff2..5f1a7efad 100644 --- a/Sources/Tests/UnitTests/Core/HighPrecisionFunctionsTest.cs +++ b/Sources/Tests/UnitTests/Core/HighPrecisionFunctionsTest.cs @@ -332,5 +332,26 @@ public void EToAPowerAtAHigherPrecisionAfterALowerOne() using var _ = MathS.Settings.DecimalPrecisionContext.Set(threeHundredDigits); Assert.StartsWith("1.4477346146633423266298972810533128989549659677348909712480333771187271973782808968904998312256362611570708175918866347464490548332052196352156710435710332682587883217473960307622422354384764392652386915303661697273096380952633917033052762392317525332683262721013424903315670646133049105751036966", MathS.Pow(MathS.e, x).EvalNumerical().ToString()); } + + /// + /// A rational written out at a hundred digits and then asked at three hundred: it kept + /// the hundred, and so did everything computed from it -- an antiderivative checked by + /// the integrator at the default precision and differentiated back at three hundred + /// missed its integrand by 1e-102. + /// https://github.com/asc-community/AngouriMath/issues/1868 + /// + [Fact] + public void ARationalAtAHigherPrecisionAfterALowerOne() + { + var fraction = Rational.Create(128, 12167); + // Written out at the default precision first, as the integrator's own check does. + Assert.Equal(fraction.ERational.ToEDecimal(MathS.Settings.DecimalPrecisionContext), fraction.EDecimal); + using var _ = MathS.Settings.DecimalPrecisionContext.Set(threeHundredDigits); + Assert.Equal(fraction.ERational.ToEDecimal(threeHundredDigits), fraction.EDecimal); + // And what is computed from it, to the last few of the three hundred digits. + var fresh = ((Real)((Entity)"128 * sqrt(46) / 12167").EvalNumerical()).EDecimal; + var computed = ((Real)(MathS.Sqrt(46) * fraction).EvalNumerical()).EDecimal; + Assert.True(computed.Subtract(fresh, threeHundredDigits).Abs().CompareTo(EDecimal.Create(1, -295)) < 0, $"{computed} and {fresh}"); + } } } From aa2664232ec105ec0463e05b29b6acb54b1e6f17 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 13 Oct 2026 06:49:39 +0000 Subject: [PATCH 2/2] A rational at a higher precision in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 299924a02..f2b0fc530 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -95,6 +95,20 @@ and the complex arcsine's real part moved two units away. A caller comparing a h result to a pinned string will see the last digits differ; a caller comparing to a tolerance will not. [#1338](https://github.com/asc-community/AngouriMath/issues/1338). +### A rational evaluated at a higher precision than before has all of its digits + +**More digits where there were a hundred.** A rational kept its decimal value in the precision current when it was +first written out: in 2.5.0 that was when it was made, and on the unreleased master when it was first asked for. Under +a higher `DecimalPrecisionContext` it went on answering about a hundred digits, and so did everything computed from it. +That includes every rational in an antiderivative, because the integrator checks its answer at the default precision. +It is written out again in the precision asked for now +([#1868](https://github.com/asc-community/AngouriMath/issues/1868)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `Rational.Create(128, 12167)`'s `EDecimal`, read at the default precision and then under 300 digits | 100 digits | 300 digits | +| `sqrt(46) * 128/12167` evaluated under 300 digits, with that rational | relative error `4.7e-101` | correct to 300 digits | + ### The last digits of an evaluated logarithm, exponential, real power or complex power **Silent, and in the hundredth digit.** `ln`, `log`, `e^x`, a real base to a real power and the