Skip to content

Commit

Permalink
Drop half-extended GCD algorithm (#9).
Browse files Browse the repository at this point in the history
The core module should be a collection of ideas, not a collection of optimizations.
  • Loading branch information
oscbyspro committed Jun 12, 2024
1 parent 15c787a commit d3cae60
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//=----------------------------------------------------------------------------=

//*============================================================================*
// MARK: + Binary Integer x Euclidean (GCD)
// MARK: + Binary Integer x Factorization
//*============================================================================*

extension FiniteInteger {
Expand All @@ -21,30 +21,14 @@ extension FiniteInteger {
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// - Requires: `!self.isInfinite && !other.isInfinite`
///
@inlinable public consuming func euclidean(_ other: consuming Self) -> Magnitude {
Self.euclidean(Finite(unchecked: self), Finite(unchecked: other))
}

/// Returns the greatest common divisor and `1` Bézout coefficient.
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// - Note: The `XGCD` algorithm behaves well for all finite unsigned inputs.
///
/// - Requires: `!self.isInfinite && !other.isInfinite`
///
/// ### Bézout's identity
/// ### Gretest Common Divisor to Least Common Multiple
///
/// ```swift
/// divisor == lhs * lhsCoefficient + rhs * rhsCoefficient
/// gcd(a, b) * lcm(a, b) == |a| * |b|
/// ```
///
/// - Note: This equation is mathematical and subject to overflow.
///
@inlinable public consuming func euclidean1(_ other: consuming Self) -> XGCD1 where Self: UnsignedInteger {
Self.euclidean1(Finite(unchecked: self), Finite(unchecked: other))
@inlinable public consuming func euclidean(_ other: consuming Self) -> Magnitude {
Self.euclidean(Finite(unchecked: self), Finite(unchecked: other))
}

/// Returns the greatest common divisor and `2` Bézout coefficients.
Expand All @@ -53,8 +37,6 @@ extension FiniteInteger {
///
/// - Note: The `XGCD` algorithm behaves well for all finite unsigned inputs.
///
/// - Requires: `!self.isInfinite && !other.isInfinite`
///
/// ### Bézout's identity
///
/// ```swift
Expand All @@ -63,8 +45,14 @@ extension FiniteInteger {
///
/// - Note: This equation is mathematical and subject to overflow.
///
@inlinable public consuming func euclidean2(_ other: consuming Self) -> XGCD2 where Self: UnsignedInteger {
Self.euclidean2(Finite(unchecked: self), Finite(unchecked: other))
@inlinable public consuming func bezout(
_ other: consuming Self
) -> (
divisor: Magnitude,
lhsCoefficient: Signitude,
rhsCoefficient: Signitude
) where Self: UnsignedInteger {
Self.bezout(Finite(unchecked: self), Finite(unchecked: other))
}
}

Expand All @@ -74,12 +62,6 @@ extension FiniteInteger {

extension BinaryInteger {

/// A greatest common divisor and `1` Bézout coefficient.
public typealias XGCD1 = (divisor: Magnitude, lhsCoefficient: Signitude)

/// A greatest common divisor and `2` Bézout coefficient.
public typealias XGCD2 = (divisor: Magnitude, lhsCoefficient: Signitude, rhsCoefficient: Signitude)

//=------------------------------------------------------------------------=
// MARK: Utilities
//=------------------------------------------------------------------------=
Expand All @@ -88,6 +70,12 @@ extension BinaryInteger {
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// ### Gretest Common Divisor to Least Common Multiple
///
/// ```swift
/// gcd(a, b) * lcm(a, b) == |a| * |b|
/// ```
///
@inlinable public static func euclidean(
_ lhs: consuming Finite<Self>,
_ rhs: consuming Finite<Self>
Expand All @@ -103,40 +91,6 @@ extension BinaryInteger {
return value.magnitude() as Magnitude
}

/// Returns the greatest common divisor and `1` Bézout coefficient.
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// - Note: The `XGCD` algorithm behaves well for all finite unsigned inputs.
///
/// ### Bézout's identity
///
/// ```swift
/// divisor == lhs * lhsCoefficient + rhs * rhsCoefficient
/// ```
///
/// - Note: This equation is mathematical and subject to overflow.
///
@inlinable public static func euclidean1(
_ lhs: consuming Finite<Self>,
_ rhs: consuming Finite<Self>
) -> XGCD1 where Self: UnsignedInteger {

var value = (consume lhs).value
var other = (consume rhs).value

var x: (Signitude, Signitude) = (1, 0)

// note that the bit cast may overflow in the final iteration
dividing: while !other.isZero {
let (division) = (value).division(Divisor(unchecked: copy other)).unchecked()
(value, other) = (other, division.remainder)
x = (x.1, x.0 &- x.1 &* Signitude(raw: division.quotient))
}

return (divisor: value, lhsCoefficient: x.0)
}

/// Returns the greatest common divisor and `2` Bézout coefficients.
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
Expand All @@ -151,10 +105,14 @@ extension BinaryInteger {
///
/// - Note: This equation is mathematical and subject to overflow.
///
@inlinable public static func euclidean2(
@inlinable public static func bezout(
_ lhs: consuming Finite<Self>,
_ rhs: consuming Finite<Self>
) -> XGCD2 where Self: UnsignedInteger {
) -> (
divisor: Magnitude,
lhsCoefficient: Signitude,
rhsCoefficient: Signitude
) where Self: UnsignedInteger {

var value = (consume lhs).value
var other = (consume rhs).value
Expand Down
2 changes: 1 addition & 1 deletion Sources/CoreKit/Models/Fallible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
/// You should not mark operations that return `Fallible<Value>` results as
/// discardable. Instead, you should always use the most appropriate recovery
/// mechanism. The `error` indicator has feelings and it would get very sad
/// if you were to forget about them.
/// if you were to forget about it.
///
@inlinable public consuming func discard() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import CoreKit

//*============================================================================*
// MARK: * Test x Euclidean
// MARK: * Test x Factorization
//*============================================================================*

extension Test {
Expand All @@ -19,49 +19,51 @@ extension Test {
// MARK: Utilities
//=------------------------------------------------------------------------=

public func euclidean<T>(_ lhs: T, _ rhs: T, _ gcd: T.Magnitude?) where T: BinaryInteger {
public func euclidean<T>(_ lhs: T, _ rhs: T, _ expectation: T.Magnitude?) where T: BinaryInteger {
//=--------------------------------------=
typealias M = T.Magnitude
//=--------------------------------------=
if let gcd, !lhs.isInfinite, !rhs.isInfinite {
if let expectation, !lhs.isInfinite, !rhs.isInfinite {
let lhs = Finite(lhs)
let rhs = Finite(rhs)

let lhsMagnitude = lhs.magnitude()
let rhsMagnitude = rhs.magnitude()

let result = T.euclidean (lhs, rhs)
let result1 = M.euclidean1(lhsMagnitude, rhsMagnitude)
let result2 = M.euclidean2(lhsMagnitude, rhsMagnitude)
let euclidean = T.euclidean(lhs, rhs)
let bezout = M.bezout(lhsMagnitude, rhsMagnitude)

same(result, gcd, "euclidean [0]")
same(result, result1.divisor, "euclidean [1]")
same(result, result2.divisor, "euclidean [2]")
same(result1.lhsCoefficient, result2.lhsCoefficient, "euclidean [3]")
same(euclidean, expectation, "euclidean [0]")
same(euclidean, bezout.divisor, "euclidean [1]")

if T.isSigned {
let lhsComplement = Finite(lhs.value.complement())
let rhsComplement = Finite(rhs.value.complement())

same(result, T.euclidean(lhs, rhsComplement), "euclidean [4]")
same(result, T.euclidean(lhsComplement, rhs), "euclidean [5]")
same(result, T.euclidean(lhsComplement, rhsComplement), "euclidean [6]")
same(euclidean, T.euclidean(lhs, rhsComplement), "euclidean [2]")
same(euclidean, T.euclidean(lhsComplement, rhs), "euclidean [3]")
same(euclidean, T.euclidean(lhsComplement, rhsComplement), "euclidean [4]")
}

if IX.size > T.size {
let a = IX(lhs.value) * IX(result2.lhsCoefficient)
let b = IX(rhs.value) * IX(result2.rhsCoefficient)
same(result, T.Magnitude(a + b), "bézout")
let a = IX(lhs.value) * IX(bezout.lhsCoefficient)
let b = IX(rhs.value) * IX(bezout.rhsCoefficient)
same(euclidean, T.Magnitude(a + b), "bézout identity")
}

always: do {
let a = lhsMagnitude.value &* M(raw: result2.lhsCoefficient)
let b = rhsMagnitude.value &* M(raw: result2.rhsCoefficient)
same(result, a &+ b, "wrapping bézout")
let a = lhsMagnitude.value &* M(raw: bezout.lhsCoefficient)
let b = rhsMagnitude.value &* M(raw: bezout.rhsCoefficient)
same(euclidean, a &+ b, "wrapping bézout identity")
}


if !expectation.isZero {
same(lhsMagnitude.value % expectation, M.zero, "a % GCD(a, b) == 0")
same(rhsMagnitude.value % expectation, M.zero, "b % GCD(a, b) == 0")
}

} else {
none(gcd, "GCD(inf, x) and GCD(x, inf) are bad")
none(expectation, "GCD(inf, x) and GCD(x, inf) are bad")
}
}
}
Loading

0 comments on commit d3cae60

Please sign in to comment.