Skip to content

Commit 6b486ca

Browse files
authored
Merge pull request #117 from linksplatform/issue-59-4b426f16
Keep Factorial method and add tests to verify precalculated constants
2 parents b28480c + 00f106b commit 6b486ca

1 file changed

Lines changed: 137 additions & 1 deletion

File tree

csharp/Platform.Numbers.Tests/MathTests.cs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,93 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Net.Http;
5+
using System.Numerics;
6+
using System.Text.RegularExpressions;
27
using Xunit;
38

49
namespace Platform.Numbers.Tests
510
{
611
public static class MathTests
712
{
13+
private static readonly TimeSpan ComputationTimeLimit = TimeSpan.FromSeconds(10);
14+
private static readonly HttpClient HttpClient = new();
15+
16+
// Fetches all entries from an OEIS b-file (plain text format: "n value" per line).
17+
// Returns a dictionary mapping index -> value as BigInteger.
18+
// The b-file URL format is: https://oeis.org/AXXXXXX/b000XXX.txt
19+
private static Dictionary<ulong, BigInteger> FetchOeisSequence(string bFileUrl)
20+
{
21+
var result = new Dictionary<ulong, BigInteger>();
22+
string content = HttpClient.GetStringAsync(bFileUrl).GetAwaiter().GetResult();
23+
foreach (var line in content.Split('\n'))
24+
{
25+
var trimmed = line.Trim();
26+
if (trimmed.StartsWith("#") || trimmed.Length == 0)
27+
{
28+
continue;
29+
}
30+
var parts = Regex.Split(trimmed, @"\s+");
31+
if (parts.Length >= 2 && ulong.TryParse(parts[0], out var index) && BigInteger.TryParse(parts[1], out var value))
32+
{
33+
result[index] = value;
34+
}
35+
}
36+
return result;
37+
}
38+
39+
// Lazily-fetched OEIS sequences, cached for the lifetime of the test run.
40+
private static Dictionary<ulong, BigInteger>? _oeisFactorials;
41+
private static Dictionary<ulong, BigInteger> OeisFactorials =>
42+
_oeisFactorials ??= FetchOeisSequence("https://oeis.org/A000142/b000142.txt");
43+
44+
private static Dictionary<ulong, BigInteger>? _oeisCatalans;
45+
private static Dictionary<ulong, BigInteger> OeisCatalans =>
46+
_oeisCatalans ??= FetchOeisSequence("https://oeis.org/A000108/b000108.txt");
47+
48+
// Computes factorial of n using BigInteger so it never overflows.
49+
// Returns null if computation exceeds the time limit.
50+
private static BigInteger? ComputeFactorialWithTimeLimit(ulong n)
51+
{
52+
var stopwatch = Stopwatch.StartNew();
53+
BigInteger result = BigInteger.One;
54+
for (ulong i = 2; i <= n; i++)
55+
{
56+
result *= i;
57+
if (stopwatch.Elapsed > ComputationTimeLimit)
58+
{
59+
return null;
60+
}
61+
}
62+
return result;
63+
}
64+
65+
// Computes the nth Catalan number from scratch using the formula:
66+
// C(n) = (2n)! / ((n+1)! * n!)
67+
// Returns null if computation exceeds the time limit.
68+
private static BigInteger? ComputeCatalanWithTimeLimit(ulong n)
69+
{
70+
var twoNFact = ComputeFactorialWithTimeLimit(2 * n);
71+
if (twoNFact is null)
72+
{
73+
return null;
74+
}
75+
76+
var nPlusOneFact = ComputeFactorialWithTimeLimit(n + 1);
77+
if (nPlusOneFact is null)
78+
{
79+
return null;
80+
}
81+
82+
var nFact = ComputeFactorialWithTimeLimit(n);
83+
if (nFact is null)
84+
{
85+
return null;
86+
}
87+
88+
return twoNFact / (nPlusOneFact * nFact);
89+
}
90+
891
[Theory]
992
[InlineData(0ul, 1ul)]
1093
[InlineData(1ul, 1ul)]
@@ -107,5 +190,58 @@ public static void MaximumConstantsTest()
107190
Assert.Equal(20ul, Math.MaximumFactorialNumber);
108191
Assert.Equal(36ul, Math.MaximumCatalanIndex);
109192
}
193+
194+
[Fact]
195+
public static void PrecalculatedFactorialsMatchComputedValues()
196+
{
197+
// Verify that every precalculated factorial constant in Math._factorials is actually
198+
// a correct factorial value. For values that can be computed locally within 10 seconds,
199+
// an independent BigInteger computation is used. For values that take longer, the
200+
// expected value is fetched from OEIS A000142 (https://oeis.org/A000142) via HTTP.
201+
for (ulong n = 0; n <= Math.MaximumFactorialNumber; n++)
202+
{
203+
var computed = ComputeFactorialWithTimeLimit(n);
204+
BigInteger expected;
205+
if (computed is null)
206+
{
207+
// Computation exceeded 10 seconds — verify against OEIS A000142.
208+
Assert.True(OeisFactorials.TryGetValue(n, out expected),
209+
$"OEIS A000142 did not contain a value for n={n}");
210+
}
211+
else
212+
{
213+
expected = computed.Value;
214+
}
215+
var precalculated = Math.Factorial<ulong>(n);
216+
Assert.Equal((ulong)expected, precalculated);
217+
}
218+
}
219+
220+
[Fact]
221+
public static void PrecalculatedCatalansMatchComputedFactorials()
222+
{
223+
// Verify that every precalculated Catalan constant in Math._catalans is actually
224+
// a correct Catalan number. For values that can be computed locally within 10 seconds
225+
// using the formula C(n) = (2n)! / ((n+1)! * n!), an independent BigInteger computation
226+
// is used. For values that take longer, the expected value is fetched from OEIS A000108
227+
// (https://oeis.org/A000108) via HTTP.
228+
for (ulong n = 0; n <= Math.MaximumCatalanIndex; n++)
229+
{
230+
var computed = ComputeCatalanWithTimeLimit(n);
231+
BigInteger expected;
232+
if (computed is null)
233+
{
234+
// Computation exceeded 10 seconds — verify against OEIS A000108.
235+
Assert.True(OeisCatalans.TryGetValue(n, out expected),
236+
$"OEIS A000108 did not contain a value for n={n}");
237+
}
238+
else
239+
{
240+
expected = computed.Value;
241+
}
242+
var precalculated = Math.Catalan<ulong>(n);
243+
Assert.Equal((ulong)expected, precalculated);
244+
}
245+
}
110246
}
111-
}
247+
}

0 commit comments

Comments
 (0)