From 52a1e448115feb67a2d42cb43e1fe5009636e8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 10 Sep 2025 18:06:08 +0200 Subject: [PATCH] Test batch Mersenne residue computation --- .../Gpu/GpuUInt128Pow2Minus1ModTests.cs | 34 +++++ PerfectNumbers.Core/Gpu/GpuKernelPool.cs | 2 +- PerfectNumbers.Core/Gpu/GpuUInt128.cs | 121 ++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 PerfectNumbers.Core.Tests/Gpu/GpuUInt128Pow2Minus1ModTests.cs diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuUInt128Pow2Minus1ModTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuUInt128Pow2Minus1ModTests.cs new file mode 100644 index 0000000..f8d9c28 --- /dev/null +++ b/PerfectNumbers.Core.Tests/Gpu/GpuUInt128Pow2Minus1ModTests.cs @@ -0,0 +1,34 @@ +using FluentAssertions; +using PerfectNumbers.Core.Gpu; +using Xunit; + +namespace PerfectNumbers.Core.Tests; + +[Trait("Category", "Fast")] +public class GpuUInt128Pow2Minus1ModTests +{ + [Fact] + public void Pow2Minus1Mod_matches_pow2mod_minus_one() + { + ulong exponent = 23UL; + GpuUInt128 modulus = new(0UL, 97UL); + var pow = GpuUInt128.Pow2Mod(exponent, modulus); + var expected = pow.IsZero ? modulus.Sub(GpuUInt128.One) : pow.Sub(GpuUInt128.One); + var actual = GpuUInt128.Pow2Minus1Mod(exponent, modulus); + actual.Should().Be(expected); + } + + [Fact] + public void Pow2Minus1ModBatch_matches_individual() + { + ulong[] exponents = { 1UL, 2UL, 3UL, 4UL, 5UL, 6UL, 7UL, 8UL }; + Span results = stackalloc GpuUInt128[exponents.Length]; + GpuUInt128 modulus = new(0UL, 97UL); + GpuUInt128.Pow2Minus1ModBatch(modulus, exponents, results); + for (int i = 0; i < exponents.Length; i++) + { + var single = GpuUInt128.Pow2Minus1Mod(exponents[i], modulus); + results[i].Should().Be(single); + } + } +} diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index 66f5465..d386c6f 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -326,7 +326,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne return; } } - if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One) + if (GpuUInt128.Pow2Minus1Mod(exponent, q) != GpuUInt128.Zero) { orders[index] = 0UL; return; diff --git a/PerfectNumbers.Core/Gpu/GpuUInt128.cs b/PerfectNumbers.Core/Gpu/GpuUInt128.cs index 45d68c3..5ccd4b1 100644 --- a/PerfectNumbers.Core/Gpu/GpuUInt128.cs +++ b/PerfectNumbers.Core/Gpu/GpuUInt128.cs @@ -214,6 +214,127 @@ public readonly struct GpuUInt128 : IComparable, IEquatable= 0) + { + result -= modulus; + } + + // result = (result + 1) % modulus + result += One; + if (result.CompareTo(modulus) >= 0) + { + result -= modulus; + } + + i++; + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Pow2Minus1ModBatch(GpuUInt128 modulus, ReadOnlySpan exponents, Span results) + { + GpuUInt128 r0 = Zero, r1 = Zero, r2 = Zero, r3 = Zero, r4 = Zero, r5 = Zero, r6 = Zero, r7 = Zero; + ulong e0 = 0UL, e1 = 0UL, e2 = 0UL, e3 = 0UL, e4 = 0UL, e5 = 0UL, e6 = 0UL, e7 = 0UL; + bool a0 = false, a1 = false, a2 = false, a3 = false, a4 = false, a5 = false, a6 = false, a7 = false; + + int len = exponents.Length; + if (len > 0) { e0 = exponents[0]; a0 = true; } + if (len > 1) { e1 = exponents[1]; a1 = true; } + if (len > 2) { e2 = exponents[2]; a2 = true; } + if (len > 3) { e3 = exponents[3]; a3 = true; } + if (len > 4) { e4 = exponents[4]; a4 = true; } + if (len > 5) { e5 = exponents[5]; a5 = true; } + if (len > 6) { e6 = exponents[6]; a6 = true; } + if (len > 7) { e7 = exponents[7]; a7 = true; } + + ulong max = 0UL; + if (a0 && e0 > max) max = e0; + if (a1 && e1 > max) max = e1; + if (a2 && e2 > max) max = e2; + if (a3 && e3 > max) max = e3; + if (a4 && e4 > max) max = e4; + if (a5 && e5 > max) max = e5; + if (a6 && e6 > max) max = e6; + if (a7 && e7 > max) max = e7; + + for (ulong i = 0UL; i < max; i++) + { + if (a0) + { + r0 += r0; if (r0.CompareTo(modulus) >= 0) r0 -= modulus; + r0 += One; if (r0.CompareTo(modulus) >= 0) r0 -= modulus; + if (i + 1UL == e0) a0 = false; + } + if (a1) + { + r1 += r1; if (r1.CompareTo(modulus) >= 0) r1 -= modulus; + r1 += One; if (r1.CompareTo(modulus) >= 0) r1 -= modulus; + if (i + 1UL == e1) a1 = false; + } + if (a2) + { + r2 += r2; if (r2.CompareTo(modulus) >= 0) r2 -= modulus; + r2 += One; if (r2.CompareTo(modulus) >= 0) r2 -= modulus; + if (i + 1UL == e2) a2 = false; + } + if (a3) + { + r3 += r3; if (r3.CompareTo(modulus) >= 0) r3 -= modulus; + r3 += One; if (r3.CompareTo(modulus) >= 0) r3 -= modulus; + if (i + 1UL == e3) a3 = false; + } + if (a4) + { + r4 += r4; if (r4.CompareTo(modulus) >= 0) r4 -= modulus; + r4 += One; if (r4.CompareTo(modulus) >= 0) r4 -= modulus; + if (i + 1UL == e4) a4 = false; + } + if (a5) + { + r5 += r5; if (r5.CompareTo(modulus) >= 0) r5 -= modulus; + r5 += One; if (r5.CompareTo(modulus) >= 0) r5 -= modulus; + if (i + 1UL == e5) a5 = false; + } + if (a6) + { + r6 += r6; if (r6.CompareTo(modulus) >= 0) r6 -= modulus; + r6 += One; if (r6.CompareTo(modulus) >= 0) r6 -= modulus; + if (i + 1UL == e6) a6 = false; + } + if (a7) + { + r7 += r7; if (r7.CompareTo(modulus) >= 0) r7 -= modulus; + r7 += One; if (r7.CompareTo(modulus) >= 0) r7 -= modulus; + if (i + 1UL == e7) a7 = false; + } + } + + if (len > 0) results[0] = r0; + if (len > 1) results[1] = r1; + if (len > 2) results[2] = r2; + if (len > 3) results[3] = r3; + if (len > 4) results[4] = r4; + if (len > 5) results[5] = r5; + if (len > 6) results[6] = r6; + if (len > 7) results[7] = r7; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public GpuUInt128 AddMod(GpuUInt128 value, ulong modulus) {