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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ 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 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
`1e-16`, whatever the precision, while it became the integer it was near only within half the working digits. So at two
thousand digits `ln(e^a + sqrt(2) e^(a/2) + 1)` was exactly `a`, where it is `a + 1.2e-67`, and the derivative of a right
antiderivative of `1/sqrt(a + i a sinh(c + d x))` at a large argument lost its imaginary part, so that a check by
evaluation read the antiderivative as wrong. A value is now that rational only within half the working digits, as for an
integer. A number from a `double`, a `float` or a `decimal` carries its type's digits, not the working precision's, and
is rounded as before: `(Entity)(1m / 3)` is still `1/3` ([#1944](https://github.com/asc-community/AngouriMath/issues/1944)).

| Input, at the default hundred digits unless stated | Was (2.6.0) | Now |
|---|---|---|
| `"1/3 + 1e-30".EvalNumerical()` | `1/3` | `0.33333333333333333333333333333433…` |
| `"0.333333333333333333333".EvalNumerical()` | `1/3` | `0.333333333333333333333` |
| `"1.000000000000000000001".EvalNumerical()` | `1` | `1.000000000000000000001` |
| The derivative of the antiderivative of `1/sqrt(a + i*a*sinh(c + d*x))` at `a, c, d, x = 21/10, 19/10, 17/10, 227283/1250`, at 2000 digits | `2.0185464…E-68` | `2.0185464…E-68 - 2.0185464…E-68i` |
| `"1/3 + 1e-60".EvalNumerical()`, `"sin(pi)".EvalNumerical()`, `(Entity)(1.0 / 3)` | `1/3`, `0`, `1/3` | `1/3`, `0`, `1/3` |

---

## 2.6.0 — since 2.5.0
Expand Down
8 changes: 4 additions & 4 deletions Sources/AngouriMath/Convenience/AngouriMathExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ public static Set Solve(this string expr, Variable var)
/// 0.100000001490116119384765625
/// </code>
/// </example>
public static Real ToNumber(this float value) => Real.Create(EDecimal.FromSingle(value));
public static Real ToNumber(this float value) => Real.FromMachineNumber(EDecimal.FromSingle(value));

/// <summary>
/// Converts an <see cref="double"/> into an AM's understandable <see cref="Real"/>
Expand All @@ -639,7 +639,7 @@ public static Set Solve(this string expr, Variable var)
/// 1/2
/// </code>
/// </example>
public static Real ToNumber(this double value) => Real.Create(EDecimal.FromDouble(value));
public static Real ToNumber(this double value) => Real.FromMachineNumber(EDecimal.FromDouble(value));

/// <summary>
/// Converts an <see cref="decimal"/> into an AM's understandable <see cref="Real"/>
Expand All @@ -659,7 +659,7 @@ public static Set Solve(this string expr, Variable var)
/// 0.1000000000000000055511151231257827021181583404541015625
/// </code>
/// </example>
public static Real ToNumber(this decimal value) => Real.Create(EDecimal.FromDecimal(value));
public static Real ToNumber(this decimal value) => Real.FromMachineNumber(EDecimal.FromDecimal(value));

/// <summary>
/// Converts PeterO's <see cref="EDecimal"/> into an AM's understandable <see cref="Real"/>
Expand Down Expand Up @@ -695,7 +695,7 @@ public static Set Solve(this string expr, Variable var)
/// </code>
/// </example>
public static Complex ToNumber(this NumericsComplex complex)
=> Complex.Create(EDecimal.FromDouble(complex.Real), EDecimal.FromDouble(complex.Imaginary));
=> Complex.FromMachineNumbers(EDecimal.FromDouble(complex.Real), EDecimal.FromDouble(complex.Imaginary));

/// <summary>
/// Converts an <see cref="bool"/> into an AM's understandable <see cref="Boolean"/>
Expand Down
4 changes: 2 additions & 2 deletions Sources/AngouriMath/Convenience/MathS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4583,7 +4583,7 @@ public static class Numbers
/// </code>
/// </example>
public static Complex Create(NumericsComplex value) =>
Create(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary));
Complex.FromMachineNumbers(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary));

/// <summary>Creates an instance of <see cref="Integer"/> from a <see cref="long"/></summary>
/// <param name="value">A value of type <see cref="long"/> (signed 64-bit integer)</param>
Expand Down Expand Up @@ -4916,7 +4916,7 @@ public static Rational CreateRational(EInteger numerator, EInteger denominator)
/// EDecimal i9 = ((Entity.Number.Real)FromString("1 + 5").EvalNumerical()).EDecimal;
/// </code>
/// </example>
public static Real Create(double value) => Real.Create(EDecimal.FromDouble(value));
public static Real Create(double value) => Real.FromMachineNumber(EDecimal.FromDouble(value));

/// <summary>
/// Creates an instance of <see cref="Complex"/> from two <see cref="EDecimal"/>s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,27 @@ public static Complex Create(Real real, Real imaginary) =>
/// <summary>
/// Creates an instance of Complex
/// </summary>
public static Complex Create(EDecimal real, EDecimal imaginary)
public static Complex Create(EDecimal real, EDecimal imaginary) => Create(real, imaginary, fromMachineNumbers: false);

/// <summary>
/// A number from a pair of <see cref="double"/>s, each part rounded as
/// <see cref="Real.FromMachineNumber(EDecimal)"/> rounds it.
/// </summary>
internal static Complex FromMachineNumbers(EDecimal real, EDecimal imaginary) => Create(real, imaginary, fromMachineNumbers: true);

private static Complex Create(EDecimal real, EDecimal imaginary, bool fromMachineNumbers)
{
if (!MathS.Settings.DowncastingEnabled)
return new Complex(real, imaginary);
Real Part(EDecimal part) => fromMachineNumbers ? Real.FromMachineNumber(part) : Real.Create(part);
if (imaginary.IsZero)
return Real.Create(real);
return Part(real);
if (real.IsNaN() || imaginary.IsNaN())
return Real.NaN;
if (imaginary.IsFinite && imaginary.Abs().LessThan(MathS.Settings.DowncastingTolerance))
return Real.Create(real);
return Part(real);
else
return new Complex(Real.Create(real), Real.Create(imaginary));
return new Complex(Part(real), Part(imaginary));
}

/// <summary>
Expand Down Expand Up @@ -293,30 +302,30 @@ public static implicit operator Complex(EDecimal value)
/// as the value that literal actually holds and not as one tenth.
/// </summary>
public static implicit operator Complex(float value)
=> Create(EDecimal.FromSingle(value), 0);
=> FromMachineNumbers(EDecimal.FromSingle(value), EDecimal.Zero);

/// <summary>
/// The value as a <see cref="Complex"/> with no imaginary part, read as the
/// binary <see langword="double"/> it is rather than as the decimal it was
/// written as.
/// </summary>
public static implicit operator Complex(double value)
=> Create(EDecimal.FromDouble(value), 0);
=> FromMachineNumbers(EDecimal.FromDouble(value), EDecimal.Zero);

/// <summary>
/// The value as a <see cref="Complex"/> with no imaginary part. A
/// <see langword="decimal"/> is decimal already, so this one keeps the digits
/// that were written.
/// </summary>
public static implicit operator Complex(decimal value)
=> Create(EDecimal.FromDecimal(value), 0);
=> FromMachineNumbers(EDecimal.FromDecimal(value), EDecimal.Zero);

/// <summary>
/// The .NET complex number as this one, through its two
/// <see langword="double"/> parts and their precision.
/// </summary>
public static implicit operator Complex(System.Numerics.Complex value) =>
Create(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary));
FromMachineNumbers(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary));

/// <summary>The pair read as real and imaginary parts.</summary>
public static implicit operator Complex((int re, int im) v) => Create(v.re, v.im);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,26 @@ public abstract partial record Number : ContinuousNode
/// The value as a <see cref="Real"/>, read as the binary <see langword="float"/> it
/// is — so <c>0.1f</c> becomes the value that literal holds, not one tenth.
/// </summary>
public static implicit operator Number(float value) => Real.Create(EDecimal.FromSingle(value));
public static implicit operator Number(float value) => Real.FromMachineNumber(EDecimal.FromSingle(value));

/// <summary>
/// The value as a <see cref="Real"/>, read as the binary <see langword="double"/> it
/// is rather than as the decimal it was written as.
/// </summary>
public static implicit operator Number(double value) => Real.Create(EDecimal.FromDouble(value));
public static implicit operator Number(double value) => Real.FromMachineNumber(EDecimal.FromDouble(value));

/// <summary>
/// The value as a <see cref="Real"/>. A <see langword="decimal"/> is decimal
/// already, so this one keeps the digits that were written.
/// </summary>
public static implicit operator Number(decimal value) => Real.Create(EDecimal.FromDecimal(value));
public static implicit operator Number(decimal value) => Real.FromMachineNumber(EDecimal.FromDecimal(value));

/// <summary>
/// The .NET complex number as a <see cref="Complex"/>, through its two
/// <see langword="double"/> parts and their precision.
/// </summary>
public static implicit operator Number(System.Numerics.Complex value)
=> Complex.Create(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary));
=> Complex.FromMachineNumbers(EDecimal.FromDouble(value.Real), EDecimal.FromDouble(value.Imaginary));
/// <summary>The big integer as an <see cref="Integer"/>, exactly and at any size.</summary>
/// <remarks>
/// Read as the two's-complement bytes it is, which is what
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ public static Rational Create(ERational value)
/// <see cref="Rational"/> if found, <see langword="null"/> otherwise.
/// </returns>
public static Rational? FindRational(EDecimal num, int iterCount = int.MinValue)
=> FindRationalWithin(num, MathS.Settings.PrecisionErrorZeroRange, iterCount);

/// <summary>
/// <see cref="FindRational(EDecimal, int)"/>, accepting a level of the continued fraction whose
/// remainder is below its integer part times <paramref name="tolerance"/>.
/// </summary>
internal static Rational? FindRationalWithin(EDecimal num, EDecimal tolerance, int iterCount = int.MinValue)
{
if (iterCount is int.MinValue)
iterCount = MathS.Settings.FloatToRationalIterCount;
Expand All @@ -179,11 +186,11 @@ public static Rational Create(ERational value)
// the exact one.
if (!MayBeASmallRational(num, iterCount))
return null;
return FindRationalExactly(num, iterCount);
return FindRationalExactly(num, iterCount, tolerance);
}

/// <summary>The continued fraction in the decimal's own arithmetic, level by level.</summary>
internal static Rational? FindRationalExactly(EDecimal num, int iterCount)
internal static Rational? FindRationalExactly(EDecimal num, int iterCount, EDecimal tolerance)
{
if (iterCount <= 0)
return null;
Expand All @@ -200,12 +207,12 @@ public static Rational Create(ERational value)
// 1e-20 came to parse as 0. Below the top, intPart is at least 1 (rest
// was inverted to get here), so the test stays as permissive as it was.
if (rest.Abs().LessThan(EDecimal.FromEInteger(intPart)
.Multiply(MathS.Settings.PrecisionErrorZeroRange, MathS.Settings.DecimalPrecisionContext)))
.Multiply(tolerance, MathS.Settings.DecimalPrecisionContext)))
return Integer.Create(sign * intPart);
else
{
var inv = CtxDivide(EDecimal.One, rest);
var rat = FindRationalExactly(inv, iterCount - 1);
var rat = FindRationalExactly(inv, iterCount - 1, tolerance);
if (rat is null)
return null;
return new Rational((intPart * sign + sign / rat.ERational).InLowestTerms());
Expand All @@ -223,8 +230,8 @@ public static Rational Create(ERational value)
/// <remarks>
/// <para>
/// The exact search accepts a level whose remainder is below the integer part
/// times <see cref="MathS.Settings.PrecisionErrorZeroRange"/>, ten to the minus
/// sixteen. In a double the remainder at a level carries an error of the value's
/// times its tolerance, at most <see cref="MathS.Settings.PrecisionErrorZeroRange"/>,
/// ten to the minus sixteen, unless a caller has set it higher. In a double the remainder at a level carries an error of the value's
/// representation error, two to the minus fifty-two, times the square of that
/// level's convergent denominator; the tolerance here is the exact one widened by
/// that bound, so a value the exact search would accept is never refused, and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,26 @@ private protected Real() : base(null, null) { }
/// Creates an instance of Real
/// (one can do it by implicit conversation)
/// </summary>
public static Real Create(EDecimal value)
/// <remarks>
/// A value is the small rational it is near within half the working digits, as it is the integer it
/// is near. Within a fixed 1e-16, <c>ln(e^a + sqrt(2) e^(a/2) + 1)</c> was exactly <c>a</c> at two
/// thousand digits, where it is <c>a + 1.2e-67</c>, and a derivative built on the difference of two
/// such logarithms lost its imaginary part.
/// https://github.com/asc-community/AngouriMath/issues/1944
/// </remarks>
public static Real Create(EDecimal value) => Create(value, fromMachineNumber: false);

/// <summary>
/// A number from a <see cref="float"/>, a <see cref="double"/> or a <see cref="decimal"/>, which carries
/// that type's digits and not the working precision's: it is the small rational it is near within
/// <see cref="MathS.Settings.PrecisionErrorZeroRange"/>, ten to the minus sixteen, so that <c>1m/3</c>
/// is <c>1/3</c>. https://github.com/asc-community/AngouriMath/issues/1944
/// </summary>
internal static Real FromMachineNumber(EDecimal value) => Create(value, fromMachineNumber: true);

// The tolerance is made only past the cheap check below, which almost every value stops at: made at
// the call, it was 580 more bytes on each evaluation of a cubic at a hundred digits.
private static Real Create(EDecimal value, bool fromMachineNumber)
{
if (!MathS.Settings.DowncastingEnabled)
return new Real(value);
Expand All @@ -79,7 +98,7 @@ public static Real Create(EDecimal value)
if (intRest.GreaterThan(1 - tolerance))
return Integer.Create(intPart.Increment());

var attempt = Rational.FindRational(value);
var attempt = Rational.FindRationalWithin(value, fromMachineNumber ? MathS.Settings.PrecisionErrorZeroRange : tolerance);
if (attempt is null ||
attempt.ERational.Numerator.Abs() > MathS.Settings.MaxAbsNumeratorOrDenominatorValue ||
attempt.ERational.Denominator.Abs() > MathS.Settings.MaxAbsNumeratorOrDenominatorValue)
Expand Down Expand Up @@ -279,19 +298,19 @@ internal static bool TryParse(string s,
/// The value as a <see cref="Real"/>, read as the binary <see langword="float"/>
/// it is — so <c>0.1f</c> becomes the value that literal holds, not one tenth.
/// </summary>
public static implicit operator Real(float value) => Create(EDecimal.FromSingle(value));
public static implicit operator Real(float value) => FromMachineNumber(EDecimal.FromSingle(value));

/// <summary>
/// The value as a <see cref="Real"/>, read as the binary
/// <see langword="double"/> it is rather than as the decimal it was written as.
/// </summary>
public static implicit operator Real(double value) => Create(EDecimal.FromDouble(value));
public static implicit operator Real(double value) => FromMachineNumber(EDecimal.FromDouble(value));

/// <summary>
/// The value as a <see cref="Real"/>. A <see langword="decimal"/> is decimal
/// already, so this one keeps the digits that were written.
/// </summary>
public static implicit operator Real(decimal value) => Create(EDecimal.FromDecimal(value));
public static implicit operator Real(decimal value) => FromMachineNumber(EDecimal.FromDecimal(value));
}
}
}
Expand Down
Loading
Loading