|
1 | 1 | using System; |
| 2 | +using System.Collections.Generic; |
2 | 3 | using System.Diagnostics; |
| 4 | +using System.Net.Http; |
3 | 5 | using System.Numerics; |
| 6 | +using System.Text.RegularExpressions; |
4 | 7 | using Xunit; |
5 | 8 |
|
6 | 9 | namespace Platform.Numbers.Tests |
7 | 10 | { |
8 | 11 | public static class MathTests |
9 | 12 | { |
10 | 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"); |
11 | 47 |
|
12 | 48 | // Computes factorial of n using BigInteger so it never overflows. |
13 | 49 | // Returns null if computation exceeds the time limit. |
@@ -159,40 +195,52 @@ public static void MaximumConstantsTest() |
159 | 195 | public static void PrecalculatedFactorialsMatchComputedValues() |
160 | 196 | { |
161 | 197 | // Verify that every precalculated factorial constant in Math._factorials is actually |
162 | | - // a correct factorial value, computed independently using BigInteger arithmetic. |
163 | | - // Only computes values that finish within 10 seconds; larger values would need |
164 | | - // verification via an external trusted resource (e.g. OEIS A000142: https://oeis.org/A000142). |
| 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. |
165 | 201 | for (ulong n = 0; n <= Math.MaximumFactorialNumber; n++) |
166 | 202 | { |
167 | 203 | var computed = ComputeFactorialWithTimeLimit(n); |
| 204 | + BigInteger expected; |
168 | 205 | if (computed is null) |
169 | 206 | { |
170 | | - // Computation exceeded 10 seconds — skip and rely on external verification. |
171 | | - continue; |
| 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; |
172 | 214 | } |
173 | 215 | var precalculated = Math.Factorial<ulong>(n); |
174 | | - Assert.Equal((ulong)computed, precalculated); |
| 216 | + Assert.Equal((ulong)expected, precalculated); |
175 | 217 | } |
176 | 218 | } |
177 | 219 |
|
178 | 220 | [Fact] |
179 | 221 | public static void PrecalculatedCatalansMatchComputedFactorials() |
180 | 222 | { |
181 | 223 | // Verify that every precalculated Catalan constant in Math._catalans is actually |
182 | | - // a correct Catalan number, computed from scratch using the formula: |
183 | | - // C(n) = (2n)! / ((n+1)! * n!) |
184 | | - // Only computes values that finish within 10 seconds; larger values would need |
185 | | - // verification via an external trusted resource (e.g. OEIS A000108: https://oeis.org/A000108). |
| 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. |
186 | 228 | for (ulong n = 0; n <= Math.MaximumCatalanIndex; n++) |
187 | 229 | { |
188 | 230 | var computed = ComputeCatalanWithTimeLimit(n); |
| 231 | + BigInteger expected; |
189 | 232 | if (computed is null) |
190 | 233 | { |
191 | | - // Computation exceeded 10 seconds — skip and rely on external verification. |
192 | | - continue; |
| 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; |
193 | 241 | } |
194 | 242 | var precalculated = Math.Catalan<ulong>(n); |
195 | | - Assert.Equal((ulong)computed, precalculated); |
| 243 | + Assert.Equal((ulong)expected, precalculated); |
196 | 244 | } |
197 | 245 | } |
198 | 246 | } |
|
0 commit comments