Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,45 @@ public partial record Rational : Real, System.IComparable<Rational>
private protected Rational(ERational value) => ERational = value;

/// <summary>
/// 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 <see cref="ERational"/>, 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
/// <see cref="ERational"/>, 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).
/// </summary>
public override EDecimal EDecimal => edecimal.GetValue(static @this => @this.ERational.ToEDecimal(MathS.Settings.DecimalPrecisionContext), this);
private LazyPropertyA<EDecimal> 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;

/// <summary>One writing out and its context, replaced whole so that a reader never sees half of one.</summary>
private sealed record WrittenOut(EContext Context, EDecimal Value);

/// <summary>
/// Behind a struct equal to every other, as the caches on <see cref="Entity"/> are:
/// a record compares every field, and two equal rationals are equal whichever of
/// them has been written out.
/// </summary>
private struct WrittenOutCache : IEquatable<WrittenOutCache>
{
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;

Expand Down
21 changes: 21 additions & 0 deletions Sources/Tests/UnitTests/Core/HighPrecisionFunctionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

/// <summary>
/// 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
/// </summary>
[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}");
}
}
}
Loading