11using System ;
2+ using System . Collections . Generic ;
3+ using System . Diagnostics ;
4+ using System . Net . Http ;
5+ using System . Numerics ;
6+ using System . Text . RegularExpressions ;
27using Xunit ;
38
49namespace 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