Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std.math: change gcd's implementation to use Stein's algorithm instead of Euclid's #21077

Merged
merged 4 commits into from
Sep 24, 2024
Merged
Changes from 2 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
57 changes: 33 additions & 24 deletions lib/std/math/gcd.zig
Original file line number Diff line number Diff line change
@@ -1,41 +1,48 @@
//! Greatest common divisor (https://mathworld.wolfram.com/GreatestCommonDivisor.html)
const std = @import("std");
const expectEqual = std.testing.expectEqual;

/// Returns the greatest common divisor (GCD) of two unsigned integers (a and b) which are not both zero.
/// For example, the GCD of 8 and 12 is 4, that is, gcd(8, 12) == 4.
/// Returns the greatest common divisor (GCD) of two unsigned integers (`a` and `b`) which are not both zero.
/// For example, the GCD of `8` and `12` is `4`, that is, `gcd(8, 12) == 4`.
pub fn gcd(a: anytype, b: anytype) @TypeOf(a, b) {

// only unsigned integers are allowed and not both must be zero
comptime switch (@typeInfo(@TypeOf(a, b))) {
.Int => |int| std.debug.assert(int.signedness == .unsigned),
.ComptimeInt => {
std.debug.assert(a >= 0);
std.debug.assert(b >= 0);
},
else => unreachable,
const N = switch (@TypeOf(a, b)) {
// convert comptime_int to some sized int type for @ctz
comptime_int => std.math.IntFittingRange(@min(a, b), @max(a, b)),
else => |T| T,
};
comptime std.debug.assert(@typeInfo(N).Int.signedness == .unsigned);
Fri3dNstuff marked this conversation as resolved.
Show resolved Hide resolved

// using an optimised form of Stein's algorithm:
// https://en.wikipedia.org/wiki/Binary_GCD_algorithm
std.debug.assert(a != 0 or b != 0);
Fri3dNstuff marked this conversation as resolved.
Show resolved Hide resolved

// if one of them is zero, the other is returned
if (a == 0) return b;
if (b == 0) return a;

// init vars
var x: @TypeOf(a, b) = a;
var y: @TypeOf(a, b) = b;
var m: @TypeOf(a, b) = a;
var x: N = a;
var y: N = b;

// using the Euclidean algorithm (https://mathworld.wolfram.com/EuclideanAlgorithm.html)
while (y != 0) {
m = x % y;
x = y;
y = m;
const xz = @ctz(x);
const yz = @ctz(y);
const shift = @min(xz, yz);
x >>= @intCast(xz);
y >>= @intCast(yz);

var diff = y -% x;
while (diff != 0) : (diff = y -% x) {
// ctz is invariant under negation, we
// put it here to ease data dependencies,
// makes the CPU happy.
const zeros = @ctz(diff);
if (x > y) diff = -%diff;
y = @min(x, y);
x = diff >> @intCast(zeros);
}
return x;
return y << @intCast(shift);
}

test "gcd" {
test gcd {
const expectEqual = std.testing.expectEqual;

try expectEqual(gcd(0, 5), 5);
try expectEqual(gcd(5, 0), 5);
try expectEqual(gcd(8, 12), 4);
Expand All @@ -45,4 +52,6 @@ test "gcd" {
try expectEqual(gcd(49865, 69811), 9973);
try expectEqual(gcd(300_000, 2_300_000), 100_000);
try expectEqual(gcd(90000000_000_000_000_000_000, 2), 2);
try expectEqual(gcd(@as(u80, 90000000_000_000_000_000_000), 2), 2);
try expectEqual(gcd(300_000, @as(u32, 2_300_000)), 100_000);
}