Restore GPU-backed UInt128 pow2 Montgomery helper

This commit is contained in:
Marek Lemańczyk committed 2025-10-05 02:13:31 +02:00
1 parent 7ffd438eee
commit 0bc97937aa
11 files changed
+1561 -30

No files matched your search

@@ -0,0 +1,82 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu;
public class GpuRationalTests
{
[Fact, Trait("Category", "Fast")]
public void Constructor_ReducesFraction()
{
GpuRational value = new(6UL, 8UL);
value.Numerator.Should().Be(new GpuUInt128(3UL));
value.Denominator.Should().Be(new GpuUInt128(4UL));
}
[Fact, Trait("Category", "Fast")]
public void Addition_ReturnsExpectedValue()
{
GpuRational left = new(1UL, 3UL);
GpuRational right = new(1UL, 6UL);
GpuRational sum = left + right;
sum.Numerator.Should().Be(new GpuUInt128(1UL));
sum.Denominator.Should().Be(new GpuUInt128(2UL));
}
[Fact, Trait("Category", "Fast")]
public void Subtraction_ReturnsExpectedValue()
{
GpuRational left = new(5UL, 6UL);
GpuRational right = new(1UL, 6UL);
GpuRational difference = left - right;
difference.Numerator.Should().Be(new GpuUInt128(2UL));
difference.Denominator.Should().Be(new GpuUInt128(3UL));
}
[Fact, Trait("Category", "Fast")]
public void Multiplication_ReturnsExpectedValue()
{
GpuRational left = new(3UL, 4UL);
GpuRational right = new(2UL, 3UL);
GpuRational product = left * right;
product.Numerator.Should().Be(new GpuUInt128(1UL));
product.Denominator.Should().Be(new GpuUInt128(2UL));
}
[Fact, Trait("Category", "Fast")]
public void Division_ReturnsExpectedValue()
{
GpuRational left = new(3UL, 5UL);
GpuRational right = new(9UL, 10UL);
GpuRational quotient = left / right;
quotient.Numerator.Should().Be(new GpuUInt128(2UL));
quotient.Denominator.Should().Be(new GpuUInt128(3UL));
}
[Fact, Trait("Category", "Fast")]
public void CompareTo_DetectsOrdering()
{
GpuRational smaller = new(1UL, 4UL);
GpuRational larger = new(2UL, 3UL);
smaller.CompareTo(larger).Should().BeLessThan(0);
larger.CompareTo(smaller).Should().BeGreaterThan(0);
smaller.CompareTo(new GpuRational(1UL, 4UL)).Should().Be(0);
}
[Fact, Trait("Category", "Fast")]
public void ToDouble_ReturnsExpectedResult()
{
GpuRational value = new(1UL, 5UL);
value.ToDouble().Should().BeApproximately(0.2, 1e-12);
}
}
@@ -1,4 +1,6 @@
using System;
using System.Numerics;
using System.Reflection;
using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
@@ -12,6 +14,7 @@ public class PrimeOrderGpuHeuristicsTests
public PrimeOrderGpuHeuristicsTests()
{
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
@@ -165,6 +168,160 @@ public class PrimeOrderGpuHeuristicsTests
}
}
[Fact]
public void TryPow2ModWide_returns_overflow_for_marked_prime()
{
UInt128 prime = ((UInt128)1 << 80) + 7;
try
{
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2ModWide_returns_success_for_supported_prime()
{
UInt128 prime = ((UInt128)1 << 96) + 11;
UInt128 exponent = ((UInt128)1 << 64) + 3;
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Success);
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
remainder.Should().Be(expected);
}
[Theory]
[InlineData("193", "31")]
[InlineData("193", "255")]
[InlineData("79228162514264337593543950347", "36893488147419103251")]
public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText)
{
UInt128 modulus = UInt128.Parse(modulusText);
UInt128 exponent = UInt128.Parse(exponentText);
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
((UInt128)gpuResult).Should().Be(expected);
}
[Fact]
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 80) + 9;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
UInt128 exponent = UInt128.One << 8;
UInt128 prime = ((UInt128)1 << 70) + 3;
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
[Fact]
public void TryPow2ModBatchWide_returns_success_for_supported_prime()
{
UInt128 prime = ((UInt128)1 << 90) + 19;
UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
status.Should().Be(GpuPow2ModStatus.Success);
for (int i = 0; i < exponents.Length; i++)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
remainders[i].Should().Be(expected);
}
}
[Fact]
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 88) + 13;
UInt128[] exponents = { UInt128.One, (UInt128)5 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(UInt128.Zero);
remainders[1].Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 72) + 23;
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(UInt128.Zero);
remainders[1].Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
[Fact]
public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
{