Skip to content
Merged
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
138 changes: 137 additions & 1 deletion csharp/Platform.Numbers.Tests/MathTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,93 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Numerics;
using System.Text.RegularExpressions;
using Xunit;

namespace Platform.Numbers.Tests
{
public static class MathTests
{
private static readonly TimeSpan ComputationTimeLimit = TimeSpan.FromSeconds(10);
private static readonly HttpClient HttpClient = new();

// Fetches all entries from an OEIS b-file (plain text format: "n value" per line).
// Returns a dictionary mapping index -> value as BigInteger.
// The b-file URL format is: https://oeis.org/AXXXXXX/b000XXX.txt
private static Dictionary<ulong, BigInteger> FetchOeisSequence(string bFileUrl)
{
var result = new Dictionary<ulong, BigInteger>();
string content = HttpClient.GetStringAsync(bFileUrl).GetAwaiter().GetResult();
foreach (var line in content.Split('\n'))
{
var trimmed = line.Trim();
if (trimmed.StartsWith("#") || trimmed.Length == 0)
{
continue;
}
var parts = Regex.Split(trimmed, @"\s+");
if (parts.Length >= 2 && ulong.TryParse(parts[0], out var index) && BigInteger.TryParse(parts[1], out var value))
{
result[index] = value;
}
}
return result;
}

// Lazily-fetched OEIS sequences, cached for the lifetime of the test run.
private static Dictionary<ulong, BigInteger>? _oeisFactorials;
private static Dictionary<ulong, BigInteger> OeisFactorials =>
_oeisFactorials ??= FetchOeisSequence("https://oeis.org/A000142/b000142.txt");

private static Dictionary<ulong, BigInteger>? _oeisCatalans;
private static Dictionary<ulong, BigInteger> OeisCatalans =>
_oeisCatalans ??= FetchOeisSequence("https://oeis.org/A000108/b000108.txt");

// Computes factorial of n using BigInteger so it never overflows.
// Returns null if computation exceeds the time limit.
private static BigInteger? ComputeFactorialWithTimeLimit(ulong n)
{
var stopwatch = Stopwatch.StartNew();
BigInteger result = BigInteger.One;
for (ulong i = 2; i <= n; i++)
{
result *= i;
if (stopwatch.Elapsed > ComputationTimeLimit)
{
return null;
}
}
return result;
}

// Computes the nth Catalan number from scratch using the formula:
// C(n) = (2n)! / ((n+1)! * n!)
// Returns null if computation exceeds the time limit.
private static BigInteger? ComputeCatalanWithTimeLimit(ulong n)
{
var twoNFact = ComputeFactorialWithTimeLimit(2 * n);
if (twoNFact is null)
{
return null;
}

var nPlusOneFact = ComputeFactorialWithTimeLimit(n + 1);
if (nPlusOneFact is null)
{
return null;
}

var nFact = ComputeFactorialWithTimeLimit(n);
if (nFact is null)
{
return null;
}

return twoNFact / (nPlusOneFact * nFact);
}

[Theory]
[InlineData(0ul, 1ul)]
[InlineData(1ul, 1ul)]
Expand Down Expand Up @@ -107,5 +190,58 @@ public static void MaximumConstantsTest()
Assert.Equal(20ul, Math.MaximumFactorialNumber);
Assert.Equal(36ul, Math.MaximumCatalanIndex);
}

[Fact]
public static void PrecalculatedFactorialsMatchComputedValues()
{
// Verify that every precalculated factorial constant in Math._factorials is actually
// a correct factorial value. For values that can be computed locally within 10 seconds,
// an independent BigInteger computation is used. For values that take longer, the
// expected value is fetched from OEIS A000142 (https://oeis.org/A000142) via HTTP.
for (ulong n = 0; n <= Math.MaximumFactorialNumber; n++)
{
var computed = ComputeFactorialWithTimeLimit(n);
BigInteger expected;
if (computed is null)
{
// Computation exceeded 10 seconds — verify against OEIS A000142.
Assert.True(OeisFactorials.TryGetValue(n, out expected),
$"OEIS A000142 did not contain a value for n={n}");
}
else
{
expected = computed.Value;
}
var precalculated = Math.Factorial<ulong>(n);
Assert.Equal((ulong)expected, precalculated);
}
}

[Fact]
public static void PrecalculatedCatalansMatchComputedFactorials()
{
// Verify that every precalculated Catalan constant in Math._catalans is actually
// a correct Catalan number. For values that can be computed locally within 10 seconds
// using the formula C(n) = (2n)! / ((n+1)! * n!), an independent BigInteger computation
// is used. For values that take longer, the expected value is fetched from OEIS A000108
// (https://oeis.org/A000108) via HTTP.
for (ulong n = 0; n <= Math.MaximumCatalanIndex; n++)
{
var computed = ComputeCatalanWithTimeLimit(n);
BigInteger expected;
if (computed is null)
{
// Computation exceeded 10 seconds — verify against OEIS A000108.
Assert.True(OeisCatalans.TryGetValue(n, out expected),
$"OEIS A000108 did not contain a value for n={n}");
}
else
{
expected = computed.Value;
}
var precalculated = Math.Catalan<ulong>(n);
Assert.Equal((ulong)expected, precalculated);
}
}
}
}
}
Loading