Skip to content

Commit

Permalink
More greatest common divisor stuff (#9).
Browse files Browse the repository at this point in the history
I some added tests and banned infinite inputs.
  • Loading branch information
oscbyspro committed Jun 6, 2024
1 parent b6b90c4 commit 1857a0e
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 43 deletions.
36 changes: 26 additions & 10 deletions Sources/CoreKit/BinaryInteger+Euclidean.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ extension BinaryInteger {
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// - Requires: `!self.isInfinite && !other.isInfinite`
///
@inlinable public consuming func euclidean(_ other: consuming Self) -> Magnitude {
//=--------------------------------------=
precondition(!self .isInfinite, "GCD(inf, x) is bad")
precondition(!other.isInfinite, "GCD(x, inf) is bad")
//=--------------------------------------=
dividing: while other != .zero {
(self, other) = (copy other, self.remainder(Divisor(unchecked: other)))
}
Expand All @@ -44,7 +50,9 @@ extension UnsignedInteger {
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// - Note: The `XGCD` algorithm behaves well for all unsigned inputs.
/// - Note: The `XGCD` algorithm behaves well for all finite unsigned inputs.
///
/// - Requires: `!self.isInfinite && !other.isInfinite`
///
/// ### Bézout's identity
///
Expand All @@ -56,13 +64,16 @@ extension UnsignedInteger {
///
@inlinable public consuming func euclidean1(_ other: consuming Self)
-> (divisor: Self, lhsCoefficient: Signitude) {

//=--------------------------------------=
precondition(!self .isInfinite, "GCD(inf, x) is bad")
precondition(!other.isInfinite, "GCD(x, inf) is bad")
//=--------------------------------------=
var x: (Signitude, Signitude) = (1, 0)

// note that the bit cast may overflow in the final iteration
dividing: while other != .zero {
let (division) = self.division(Divisor(unchecked: copy other)).unchecked()

(self, other) = (other, division.remainder)
let division = self.division(Divisor(unchecked: copy other)).unchecked()
(self, other) = (other, division.remainder)
x = (x.1, x.0 &- x.1 &* Signitude(raw: division.quotient))
}

Expand All @@ -73,7 +84,9 @@ extension UnsignedInteger {
///
/// - Note: The greatest common divisor of `(0, 0)` is zero.
///
/// - Note: The `XGCD` algorithm behaves well for all unsigned inputs.
/// - Note: The `XGCD` algorithm behaves well for all finite unsigned inputs.
///
/// - Requires: `!self.isInfinite && !other.isInfinite`
///
/// ### Bézout's identity
///
Expand All @@ -85,14 +98,17 @@ extension UnsignedInteger {
///
@inlinable public consuming func euclidean2(_ other: consuming Self)
-> (divisor: Self, lhsCoefficient: Signitude, rhsCoefficient: Signitude) {

//=--------------------------------------=
precondition(!self .isInfinite, "GCD(inf, x) is bad")
precondition(!other.isInfinite, "GCD(x, inf) is bad")
//=--------------------------------------=
var x: (Signitude, Signitude) = (1, 0)
var y: (Signitude, Signitude) = (0, 1)

// note that the bit cast may overflow in the final iteration
dividing: while other != .zero {
let (division) = self.division(Divisor(unchecked: copy other)).unchecked()

(self, other) = (other, division.remainder)
let division = self.division(Divisor(unchecked: copy other)).unchecked()
(self, other) = (other, division.remainder)
x = (x.1, x.0 &- x.1 &* Signitude(raw: division.quotient))
y = (y.1, y.0 &- y.1 &* Signitude(raw: division.quotient))
}
Expand Down
27 changes: 15 additions & 12 deletions Sources/TestKit/Test+Euclidean.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,36 @@ extension Test {
// MARK: Utilities
//=------------------------------------------------------------------------=

public func euclidean<T>(_ lhs: T, _ rhs: T, _ gcd: T.Magnitude) where T: SignedInteger {
public func euclidean<T>(_ lhs: T, _ rhs: T, _ gcd: T.Magnitude) where T: BinaryInteger {
//=--------------------------------------=
let lhsMagnitude = lhs.magnitude()
let rhsMagnitude = rhs.magnitude()
//=--------------------------------------=
let result0 = lhs.euclidean(rhs)
let result = lhs.euclidean(rhs)
let result1 = lhsMagnitude.euclidean1(rhsMagnitude)
let result2 = lhsMagnitude.euclidean2(rhsMagnitude)

same(result0, result1.divisor, "euclidean [0]")
same(result0, result2.divisor, "euclidean [1]")
same(result1.lhsCoefficient, result2.lhsCoefficient, "euclidean [2]")
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(result0, lhs.euclidean(rhs.complement()), "euclidean [3]")
same(result0, lhs.complement().euclidean(rhs), "euclidean [4]")
same(result0, lhs.complement().euclidean(rhs.complement()), "euclidean [5]")
if T.isSigned {
same(result, lhs.euclidean(rhs.complement()), "euclidean [4]")
same(result, lhs.complement().euclidean(rhs), "euclidean [5]")
same(result, lhs.complement().euclidean(rhs.complement()), "euclidean [6]")
}

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

always: do {
let a = T(raw: lhsMagnitude) &* result2.lhsCoefficient
let b = T(raw: rhsMagnitude) &* result2.rhsCoefficient
same(result0, T.Magnitude(raw: a &+ b), "wrapping bézout")
let a = lhsMagnitude &* T.Magnitude(raw: result2.lhsCoefficient)
let b = rhsMagnitude &* T.Magnitude(raw: result2.rhsCoefficient)
same(result, a &+ b, "wrapping bézout")
}
}
}
23 changes: 9 additions & 14 deletions Tests/CoreKitTests/CoreInt+Euclidean.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,12 @@ extension CoreIntTests {
//=----------------------------------=
for lhs in values8 {
for rhs in values8 {
let result0 = lhs.euclidean (rhs)
let result = lhs.euclidean (rhs)
let result1 = lhs.euclidean1(rhs)
let result2 = lhs.euclidean2(rhs)

always: do {
coprime += U32(Bit(result0 == 1))
coprime += U32(Bit(result2.divisor == 1))
}

if result2.divisor == result0 {
success += 1
if result == 1 {
coprime += 1
}

always: do {
Expand All @@ -150,11 +145,11 @@ extension CoreIntTests {
success += U32(Bit((a + b) == result2.divisor))
}

if result0 == result2.divisor {
if result == result1.divisor {
success += 1
}

if result1.divisor == result2.divisor {
if result == result2.divisor {
success += 1
}

Expand All @@ -163,15 +158,15 @@ extension CoreIntTests {
}

if lhs == 0, rhs == 0 {
success += U32(Bit(result2.divisor == 0))
success += U32(Bit(result == 0))
} else {
success += U32(Bit(result2.divisor >= 1))
success += U32(Bit(result >= 1))
}
}
}

Test().same(coprime, 2 * 39641)
Test().same(success, 6 * 65536)
Test().same(coprime, 1 * 39641)
Test().same(success, 5 * 65536)
}

whereIs(U8 .self)
Expand Down
8 changes: 8 additions & 0 deletions Tests/FibonacciKitTests/Fibonacci+CoreInt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 10,
element: 00000000000000000055,
Expand All @@ -64,6 +65,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 12,
element: 00000000000000000144,
Expand All @@ -75,6 +77,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 22,
element: 00000000000000017711,
Expand All @@ -86,6 +89,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 23,
element: 00000000000000028657,
Expand All @@ -97,6 +101,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 45,
element: 00000000001134903170,
Expand All @@ -108,6 +113,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 46,
element: 00000000001836311903,
Expand All @@ -119,6 +125,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 91,
element: 04660046610375530309,
Expand All @@ -130,6 +137,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 92,
element: 07540113804746346429,
Expand Down
6 changes: 6 additions & 0 deletions Tests/FibonacciKitTests/Fibonacci+DoubleInt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 22,
element: 17711,
Expand All @@ -59,6 +60,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 45,
element: 1134903170,
Expand All @@ -70,6 +72,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 91,
element: 04660046610375530309,
Expand All @@ -81,6 +84,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 23,
element: 28657,
Expand All @@ -92,6 +96,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 46,
element: 1836311903,
Expand All @@ -103,6 +108,7 @@ extension FibonacciTests {
Case(item).checkIsLastIndex()
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
Case(item).same(
index: 92,
element: 07540113804746346429,
Expand Down
2 changes: 2 additions & 0 deletions Tests/FibonacciKitTests/Fibonacci+InfiniInt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extension FibonacciTests {
#if !DEBUG
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
#endif
Case(item).element(IXL("""
0000000179539422936879670273043077421513074187637090531654188941\
Expand Down Expand Up @@ -159,6 +160,7 @@ extension FibonacciTests {
#if !DEBUG
Case(item).checkMathInvariants()
Case(item).checkTextInvariants()
Case(item).checkSequencePairIsCoprime()
#endif
Case(item).element(UXL("""
0000000179539422936879670273043077421513074187637090531654188941\
Expand Down
18 changes: 11 additions & 7 deletions Tests/FibonacciKitTests/Fibonacci.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,6 @@ extension FibonacciTests.Case {
// MARK: Utilities x Invariants
//=------------------------------------------------------------------------=

/// Performs a description round-trip for each radix.
func checkTextInvariants() {
for radix: UX in 2 ... 36 {
test.description(roundtripping: item.element, radix: radix)
}
}

/// Generates new instances and uses them to check math invariants.
///
/// #### Invariants
Expand Down Expand Up @@ -138,6 +131,17 @@ extension FibonacciTests.Case {
}
}

/// Performs a description round-trip for each radix.
func checkTextInvariants() {
for radix: UX in 2 ... 36 {
test.description(roundtripping: item.element, radix: radix)
}
}

func checkSequencePairIsCoprime() {
test.same(item.element.euclidean(item.next), 1)
}

//=------------------------------------------------------------------------=
// MARK: Utilities x Min, Max
//=------------------------------------------------------------------------=
Expand Down
Loading

0 comments on commit 1857a0e

Please sign in to comment.