Skip to content

Commit

Permalink
std.math: change gcd's implementation to use Stein's algorithm instea…
Browse files Browse the repository at this point in the history
…d of Euclid's (ziglang#21077)
  • Loading branch information
Fri3dNstuff authored and richerfu committed Oct 28, 2024
1 parent e46827a commit aeac04e
Showing 1 changed file with 35 additions and 24 deletions.
59 changes: 35 additions & 24 deletions lib/std/math/gcd.zig
Original file line number Diff line number Diff line change
@@ -1,41 +1,50 @@
//! 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),
.comptime_int => {
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,
};
if (@typeInfo(N) != .int or @typeInfo(N).int.signedness != .unsigned) {
@compileError("`a` and `b` must be usigned integers");
}

// using an optimised form of Stein's algorithm:
// https://en.wikipedia.org/wiki/Binary_GCD_algorithm
std.debug.assert(a != 0 or b != 0);

// 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 +54,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);
}

0 comments on commit aeac04e

Please sign in to comment.