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
Original file line number Diff line number Diff line change
Expand Up @@ -672,9 +672,10 @@ private static Entity FallingFactorialOver(Number n, int k)
return product switch
{
Rational exact => exact,
Real real => Real.Create(real.EDecimal.RoundToPrecision(context)),
Complex complex => Complex.Create(complex.RealPart.EDecimal.RoundToPrecision(context),
complex.ImaginaryPart.EDecimal.RoundToPrecision(context)),
Real real => Real.CreateAtScale(real.EDecimal.RoundToPrecision(context), PeterO.Numbers.EDecimal.Zero),
Complex complex => Complex.CreateAtScales(complex.RealPart.EDecimal.RoundToPrecision(context),
complex.ImaginaryPart.EDecimal.RoundToPrecision(context),
PeterO.Numbers.EDecimal.Zero, PeterO.Numbers.EDecimal.Zero),
var other => other,
};
}
Expand Down Expand Up @@ -774,7 +775,9 @@ private static class FactorialPowers
/// exactly once it is large. Real arguments are taken as decimals throughout: a complex
/// product adds a zero imaginary part's terms, and adding to a number whose exponent is in
/// the millions aligns the two digit by digit, which made <c>binomial(10000000, 1.43)</c>
/// take longer than twenty seconds.
/// take longer than twenty seconds. The quotient is made as one that cancels nothing, and is
/// never rounded onto zero: made as any number, a value under the downcasting tolerance was
/// taken for a residual, and <c>fallingfactorial(0.5, -100.5)</c>, 9.4e-161, was <c>0</c>.
/// </summary>
internal static Complex? QuotientOfFactorials(Complex[] over, Complex[] under)
{
Expand All @@ -790,7 +793,7 @@ private static class FactorialPowers
foreach (var x in under)
if (((Real)x).EDecimal.Factorial(unbounded) is { IsFinite: true } factorial) real = real.Divide(factorial, unbounded); else return null;
}
return Real.Create(real.RoundToPrecision(context));
return Real.CreateAtScale(real.RoundToPrecision(context), PeterO.Numbers.EDecimal.Zero);
}
Complex quotient = Integer.One;
using (MathS.Settings.DowncastingEnabled.Set(false))
Expand All @@ -801,8 +804,9 @@ private static class FactorialPowers
foreach (var x in under)
if (FactorialOf(x, unbounded) is { } factorial) quotient /= factorial; else return null;
}
return Complex.Create(quotient.RealPart.EDecimal.RoundToPrecision(context),
quotient.ImaginaryPart.EDecimal.RoundToPrecision(context));
return Complex.CreateAtScales(quotient.RealPart.EDecimal.RoundToPrecision(context),
quotient.ImaginaryPart.EDecimal.RoundToPrecision(context),
PeterO.Numbers.EDecimal.Zero, PeterO.Numbers.EDecimal.Zero);
}

private static Complex? FactorialOf(Complex x, PeterO.Numbers.EContext context)
Expand Down Expand Up @@ -831,16 +835,35 @@ protected override Entity InnerSimplify(bool isExact) =>
Integer { EInteger: var n } when n.Equals(PeterO.Numbers.EInteger.FromInt32(-1)) => Rational.Create(-1, 2),
Integer => Integer.Zero,
Number value when (value + Rational.Create(1, 2)).Evaled is Integer { EInteger.Sign: <= 0 } => MathS.NaN,
// The two gammas as one quotient, and 4^n times it with no bound on the exponent:
// each gamma left the range from n near 450, where the number is near 1e267, and
// catalannumber(500.5) was +oo/+oo, which is NaN.
Complex value when !isExact
&& Number.Factorial((Complex)(value - Rational.Create(1, 2)).Evaled) is var top && Number.Factorial((Complex)(value + 1).Evaled) is var bottom
&& !top.IsNaN && !bottom.IsNaN
=> (MathS.Pow(4, value) * top / (MathS.Sqrt(MathS.pi) * bottom)).Evaled,
&& FactorialPowers.QuotientOfFactorials([(Complex)(value - Rational.Create(1, 2)).Evaled], [(Complex)(value + 1).Evaled]) is { } quotient
=> FourToThePowerTimes(value, quotient),
_ => null
},
(@this, a) => ((CatalanNumberf)@this).New(a), isExact);

/// <summary>How far a Catalan number is computed exactly before the node is left as written.</summary>
private const int LargestIndex = 1000;

/// <summary>
/// <c>4^n quotient / √π</c>. For a real <paramref name="n"/>, formed in decimals with no bound on the exponent
/// and rounded into the context: no number is made of the power itself, which for <c>n = 10^20</c> has an
/// exponent near 6e19 and cannot be read as the integer a number checks it for. A complex one is formed in
/// the context, as it was.
/// </summary>
private static Entity FourToThePowerTimes(Complex n, Complex quotient)
{
if (n is not Real { EDecimal: var power } || quotient is not Real { EDecimal: var ratio })
return (MathS.Pow(4, n) * quotient / MathS.Sqrt(MathS.pi)).Evaled;
var context = MathS.Settings.DecimalPrecisionContext.Value;
var unbounded = InternalAMExtensions.WithUnboundedExponents(context);
var value = PeterO.Numbers.EDecimal.FromInt32(4).Pow(power, unbounded).Multiply(ratio, unbounded)
.Divide(PeterO.Numbers.EDecimal.PI(unbounded).Sqrt(unbounded), unbounded);
return Real.CreateAtScale(value.RoundToPrecision(context), PeterO.Numbers.EDecimal.Zero);
}
}

public partial record HarmonicNumberf
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using System;
using System.Threading.Tasks;
using AngouriMath.Extensions;
using PeterO.Numbers;
using Xunit;

namespace AngouriMath.Tests.Convenience
{
/// <summary>
/// A Catalan number of a number that is not whole is <c>4^n Γ(n + 1/2) / (√π Γ(n + 2))</c>, and each gamma
/// was computed in the evaluation's exponent range: from <c>n = 449.5</c>, whose number is 2.5e266, both were
/// <c>+oo</c> and the quotient <c>NaN</c>, and <c>catalannumber(-60.3)</c> was <c>NaN</c> as well. And a quotient
/// of factorials under the downcasting tolerance was rounded onto zero as though it were what a cancellation left:
/// <c>fallingfactorial(0.5, -100.5)</c>, 9.4e-161, was <c>0</c>. Found by compilesweep, on
/// <c>catalannumber(Ei(sinh(x)))</c> at 6; no issue was filed. The values are mpmath's at 120 digits.
/// </summary>
[Trait("Area", "Discrete")]
public sealed class ACatalanNumberPastTheExponentRangeTest
{
[Theory]
[InlineData("catalannumber(10.5)", "31375.760336503176813184117125078909721937218359811")]
[InlineData("catalannumber(449.5)", "2.4958055762434131441628313609834440544021408905592e+266")]
[InlineData("catalannumber(500.5)", "1.0773809195193076398539141613347340467488925064819e+297")]
[InlineData("catalannumber(1600.25)", "2.462290001548975528409180780699097156132733259886e+958")]
[InlineData("catalannumber(-60.3)", "-8.3875179942165737514658221112507292321206364599213e-40")]
// A quotient of factorials under the downcasting tolerance, which is not a residual.
[InlineData("fallingfactorial(0.5, -100.5)", "9.4019927548161528235486105066852002441716212361456e-161")]
[InlineData("risingfactorial(100.5, -60.5)", "2.1883878150169778143163818514630537992056282322519e-111")]
public void IsItsValue(string input, string expected)
{
var value = Assert.IsAssignableFrom<Entity.Number.Real>(Within(input));
var reference = EDecimal.FromString(expected);
var error = value.EDecimal.Subtract(reference).Abs().Divide(reference.Abs(), EContext.Binary64);
Assert.True(error.CompareTo(EDecimal.FromString("1e-25")) < 0, $"{input} evaluated to {value}");
}

[Theory]
// 1.7e1199, and about 10^(6e39): past the range, where a real is +oo.
[InlineData("catalannumber(2000.5)")]
[InlineData("catalannumber(1e40 + 0.2)")]
public void IsPositiveInfinityPastTheRange(string input)
=> Assert.Equal(Entity.Number.Real.PositiveInfinity, Within(input));

private static Entity Within(string input)
{
var task = Task.Factory.StartNew(() => input.ToEntity().EvalNumerical(), TaskCreationOptions.LongRunning);
Assert.True(task.Wait(TimeSpan.FromSeconds(30)), $"{input} did not finish in thirty seconds");
return task.Result;
}
}
}
Loading