149 lines
4.7 KiB
Zig
149 lines
4.7 KiB
Zig
// ©AngelaMos | 2026
|
|
// numtheory.zig
|
|
|
|
const std = @import("std");
|
|
|
|
pub fn mulMod(a: u64, b: u64, m: u64) u64 {
|
|
return @intCast((@as(u128, a) * @as(u128, b)) % m);
|
|
}
|
|
|
|
pub fn modExp(base: u64, exp: u64, modulus: u64) u64 {
|
|
if (modulus == 1) return 0;
|
|
var result: u64 = 1;
|
|
var b: u64 = base % modulus;
|
|
var e: u64 = exp;
|
|
while (e > 0) {
|
|
if (e & 1 == 1) result = mulMod(result, b, modulus);
|
|
b = mulMod(b, b, modulus);
|
|
e >>= 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
pub fn isPrime(n: u64) bool {
|
|
if (n < 2) return false;
|
|
const small = [_]u64{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
|
|
for (small) |p| {
|
|
if (n == p) return true;
|
|
if (n % p == 0) return false;
|
|
}
|
|
var d: u64 = n - 1;
|
|
var r: u32 = 0;
|
|
while (d & 1 == 0) : (d >>= 1) r += 1;
|
|
for (small) |a| {
|
|
var x = modExp(a, d, n);
|
|
if (x == 1 or x == n - 1) continue;
|
|
var i: u32 = 1;
|
|
var composite = true;
|
|
while (i < r) : (i += 1) {
|
|
x = mulMod(x, x, n);
|
|
if (x == n - 1) {
|
|
composite = false;
|
|
break;
|
|
}
|
|
}
|
|
if (composite) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
pub fn smallestPrimeAbove(n: u64) u64 {
|
|
var candidate = n + 1;
|
|
if (candidate <= 2) return 2;
|
|
if (candidate & 1 == 0) candidate += 1;
|
|
while (!isPrime(candidate)) : (candidate += 2) {}
|
|
return candidate;
|
|
}
|
|
|
|
pub fn distinctPrimeFactors(value: u64, buf: []u64) []u64 {
|
|
var n = value;
|
|
var count: usize = 0;
|
|
var f: u64 = 2;
|
|
while (f * f <= n) {
|
|
if (n % f == 0) {
|
|
buf[count] = f;
|
|
count += 1;
|
|
while (n % f == 0) n /= f;
|
|
}
|
|
f += if (f == 2) 1 else 2;
|
|
}
|
|
if (n > 1) {
|
|
buf[count] = n;
|
|
count += 1;
|
|
}
|
|
return buf[0..count];
|
|
}
|
|
|
|
pub fn isPrimitiveRoot(candidate: u64, prime: u64, prime_factors: []const u64) bool {
|
|
for (prime_factors) |q| {
|
|
if (modExp(candidate, (prime - 1) / q, prime) == 1) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
pub fn findPrimitiveRoot(prime: u64, rand: std.Random) u64 {
|
|
if (prime == 2) return 1;
|
|
var buf: [64]u64 = undefined;
|
|
const factors = distinctPrimeFactors(prime - 1, &buf);
|
|
while (true) {
|
|
const candidate = rand.intRangeAtMost(u64, 2, prime - 1);
|
|
if (isPrimitiveRoot(candidate, prime, factors)) return candidate;
|
|
}
|
|
}
|
|
|
|
test "modExp known values" {
|
|
try std.testing.expectEqual(@as(u64, 1), modExp(3, 4, 5));
|
|
try std.testing.expectEqual(@as(u64, 24), modExp(2, 10, 1000));
|
|
try std.testing.expectEqual(@as(u64, 0), modExp(10, 3, 1000));
|
|
try std.testing.expectEqual(@as(u64, 445), modExp(4, 13, 497));
|
|
}
|
|
|
|
test "isPrime classifies small and large values" {
|
|
const primes = [_]u64{ 2, 3, 5, 7, 11, 13, 65537, 1009, 4294967311, 281474976710677 };
|
|
for (primes) |p| try std.testing.expect(isPrime(p));
|
|
const composites = [_]u64{ 0, 1, 4, 9, 15, 561, 1105, 4294967296, 281474976710676 };
|
|
for (composites) |c| try std.testing.expect(!isPrime(c));
|
|
}
|
|
|
|
test "smallestPrimeAbove" {
|
|
try std.testing.expectEqual(@as(u64, 257), smallestPrimeAbove(256));
|
|
try std.testing.expectEqual(@as(u64, 65537), smallestPrimeAbove(65536));
|
|
try std.testing.expectEqual(@as(u64, 1009), smallestPrimeAbove(1000));
|
|
try std.testing.expectEqual(@as(u64, 4294967311), smallestPrimeAbove(4294967296));
|
|
}
|
|
|
|
test "distinctPrimeFactors" {
|
|
var buf: [16]u64 = undefined;
|
|
try std.testing.expectEqualSlices(u64, &.{2}, distinctPrimeFactors(256, &buf));
|
|
try std.testing.expectEqualSlices(u64, &.{ 2, 3 }, distinctPrimeFactors(12, &buf));
|
|
try std.testing.expectEqualSlices(u64, &.{ 2, 5 }, distinctPrimeFactors(100, &buf));
|
|
try std.testing.expectEqualSlices(u64, &.{ 2, 3, 5 }, distinctPrimeFactors(30, &buf));
|
|
}
|
|
|
|
test "isPrimitiveRoot for p=7 (roots are 3 and 5)" {
|
|
var buf: [16]u64 = undefined;
|
|
const factors = distinctPrimeFactors(7 - 1, &buf);
|
|
try std.testing.expect(isPrimitiveRoot(3, 7, factors));
|
|
try std.testing.expect(isPrimitiveRoot(5, 7, factors));
|
|
try std.testing.expect(!isPrimitiveRoot(2, 7, factors));
|
|
try std.testing.expect(!isPrimitiveRoot(4, 7, factors));
|
|
}
|
|
|
|
test "findPrimitiveRoot returns a generator that walks the whole group" {
|
|
var prng = std.Random.DefaultPrng.init(0xA11CE_2026);
|
|
const rand = prng.random();
|
|
const primes = [_]u64{ 7, 257, 65537, 1009 };
|
|
for (primes) |p| {
|
|
const g = findPrimitiveRoot(p, rand);
|
|
var seen = [_]bool{false} ** 65537;
|
|
var cur: u64 = 1;
|
|
var k: u64 = 0;
|
|
while (k < p - 1) : (k += 1) {
|
|
cur = mulMod(cur, g, p);
|
|
try std.testing.expect(!seen[cur]);
|
|
seen[cur] = true;
|
|
}
|
|
try std.testing.expectEqual(@as(u64, 1), cur);
|
|
}
|
|
}
|