From 02cd5b3ffa29065131fe2361a3b5c93acc0da695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Sun, 29 Oct 2023 10:36:19 +0100 Subject: [PATCH] [NBKCoreKit] Extended GCD with iteration count. --- .../NBKIntegerDescription+Decoding.swift | 8 +-- ...rBinaryInteger+GreatestCommonDivisor.swift | 54 +++++++++++-------- ...rBinaryInteger+GreatestCommonDivisor.swift | 30 ++++++++--- 3 files changed, 59 insertions(+), 33 deletions(-) diff --git a/Sources/NBKCoreKit/Private/NBKIntegerDescription+Decoding.swift b/Sources/NBKCoreKit/Private/NBKIntegerDescription+Decoding.swift index 6bab76c1..1c4cf0f3 100644 --- a/Sources/NBKCoreKit/Private/NBKIntegerDescription+Decoding.swift +++ b/Sources/NBKCoreKit/Private/NBKIntegerDescription+Decoding.swift @@ -221,14 +221,14 @@ extension NBK.IntegerDescription { //=----------------------------------= backwards: while index < split.quotient { let chunk = UnsafeBufferPointer(rebasing: NBK.removeSuffix(from: &digits, count: radix.exponent())) - guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } + guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } words.baseAddress!.advanced(by: index).initialize(to: element) words.formIndex(after: &index) } backwards: if split.remainder.isMoreThanZero { let chunk = UnsafeBufferPointer(rebasing: NBK.removeSuffix(from: &digits, count: split.remainder)) - guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } + guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } words.baseAddress!.advanced(by: index).initialize(to: element) words.formIndex(after: &index) } @@ -263,14 +263,14 @@ extension NBK.IntegerDescription { //=----------------------------------= forwards: if split.remainder.isMoreThanZero { let chunk = UnsafeBufferPointer(rebasing: NBK.removePrefix(from: &digits, count: split.remainder)) - guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } + guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } words.baseAddress!.advanced(by: index).initialize(to: element) words.formIndex(after: &index) } forwards: while index < count { let chunk = UnsafeBufferPointer(rebasing: NBK.removePrefix(from: &digits, count: radix.exponent())) - guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } + guard let element: UInt = self.truncating(digits: chunk, radix: radix.base()) else { return } words.baseAddress!.advanced(by: index).initialize(to: NBK.SUISS.multiply(&words[.. (result: Integer.Magnitude, lhsCoefficient: Integer, rhsCoefficient: Integer, lhsQuotient: Integer, rhsQuotient: Integer) { + -> (result: Integer.Magnitude, lhsCoefficient: Integer, rhsCoefficient: Integer, lhsQuotient: Integer, rhsQuotient: Integer, iteration: Integer.Magnitude) { //=--------------------------------------= let lhsIsLessThanZero: Bool = lhs.isLessThanZero let rhsIsLessThanZero: Bool = rhs.isLessThanZero //=--------------------------------------= let unsigned = Magnitude.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs.magnitude, and: rhs.magnitude) + let odd = unsigned.iteration.isOdd as Bool //=--------------------------------------= - return (result: unsigned.result, - lhsCoefficient: Integer(sign: lhsIsLessThanZero != unsigned.iterationIsOdd ? .minus : .plus, magnitude: unsigned.lhsCoefficient)!, - rhsCoefficient: Integer(sign: rhsIsLessThanZero == unsigned.iterationIsOdd ? .minus : .plus, magnitude: unsigned.rhsCoefficient)!, - lhsQuotient: Integer(sign: lhsIsLessThanZero /*----------------------*/ ? .minus : .plus, magnitude: unsigned.lhsQuotient )!, - rhsQuotient: Integer(sign: rhsIsLessThanZero /*----------------------*/ ? .minus : .plus, magnitude: unsigned.rhsQuotient )!) + return ( + result: unsigned.result as Integer.Magnitude, + lhsCoefficient: Integer(sign: lhsIsLessThanZero != odd ? .minus : .plus, magnitude: unsigned.lhsCoefficient)!, + rhsCoefficient: Integer(sign: rhsIsLessThanZero == odd ? .minus : .plus, magnitude: unsigned.rhsCoefficient)!, + lhsQuotient: Integer(sign: lhsIsLessThanZero /*--*/ ? .minus : .plus, magnitude: unsigned.lhsQuotient )!, + rhsQuotient: Integer(sign: rhsIsLessThanZero /*--*/ ? .minus : .plus, magnitude: unsigned.rhsQuotient )!, + iteration: unsigned.iteration as Integer.Magnitude) } } @@ -136,15 +146,17 @@ extension NBK.ProperBinaryInteger where Integer: NBKUnsignedInteger { /// ### Result /// /// ```swift - /// precondition(0 <= result && result <= T.max) + /// precondition(0 ... T.max ~= result) /// ``` /// /// - Note: The GCD of `0` and `0` is `0`. /// + /// - Note: The GCD of `0` and `X` is `X`. + /// /// ### Bézout's identity (unsigned) /// /// ```swift - /// if !iterationIsOdd { + /// if !iteration.isOdd { /// precondition(result == (lhs &* lhsCoefficient &- rhs &* rhsCoefficient)) /// } else { /// precondition(result == (rhs &* rhsCoefficient &- lhs &* lhsCoefficient)) @@ -154,36 +166,34 @@ extension NBK.ProperBinaryInteger where Integer: NBKUnsignedInteger { /// ### Quotients of dividing by GCD /// /// ```swift - /// guard result != 0000000000 else { return } + /// guard !result.isZero else { return } /// precondition(lhsQuotient == lhs / result) /// precondition(rhsQuotient == rhs / result) /// ``` /// - /// ### Loop count result + /// ### Iteration /// /// ```swift - /// let lhsCoefficientSign = lhs.isLessThanZero != iterationIsOdd - /// let rhsCoefficientSign = rhs.isLessThanZero == iterationIsOdd + /// let lhsCoefficientSign = lhs.isLessThanZero != iteration.isOdd + /// let rhsCoefficientSign = rhs.isLessThanZero == iteration.isOdd /// ``` /// @inlinable public static func greatestCommonDivisorByExtendedEuclideanAlgorithm(of lhs: Integer, and rhs: Integer) - -> (result: Integer, lhsCoefficient: Integer, rhsCoefficient: Integer, lhsQuotient: Integer, rhsQuotient: Integer, iterationIsOdd: Bool) { + -> (result: Integer, lhsCoefficient: Integer, rhsCoefficient: Integer, lhsQuotient: Integer, rhsQuotient: Integer, iteration: Integer) { //=--------------------------------------= var (a, b) = (lhs, rhs) as (Integer,Integer) var (c, d) = (001, 000) as (Integer,Integer) var (e, f) = (000, 001) as (Integer,Integer) - var iterationIsOdd = (((((((((false))))))))) + var iteration = 0000000 as (((((Integer))))) //=--------------------------------------= while !b.isZero { - - iterationIsOdd.toggle() let division = a.quotientAndRemainder(dividingBy: b) - (a, b) = (b, division.remainder) (c, d) = (d, division.quotient * d + c) (e, f) = (f, division.quotient * f + e) + iteration += 000000001 as Integer.Digit } //=--------------------------------------= - return (result: a, lhsCoefficient: c, rhsCoefficient: e, lhsQuotient: f, rhsQuotient: d, iterationIsOdd: iterationIsOdd) + return (result: a, lhsCoefficient: c, rhsCoefficient: e, lhsQuotient: f, rhsQuotient: d, iteration: iteration) } } diff --git a/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift index 2ec290ff..61623254 100644 --- a/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift +++ b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift @@ -117,26 +117,39 @@ final class NBKProperBinaryIntegerTestsOnGreatestCommonDivisorByExtendedEuclidea func testWrappingBézoutIdentityWorksForAllPairsOfInt8() { self.continueAfterFailure = false + var maxIteration: UInt8 = 0000000 + for lhs in Int8.min ... Int8.max { for rhs in Int8.min ... Int8.max { let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) + maxIteration = Swift.max(maxIteration, extended.iteration) XCTAssertEqual(Int8(bitPattern: extended.result), lhs &* extended.lhsCoefficient &+ rhs &* extended.rhsCoefficient) + XCTAssertEqual(extended.lhsCoefficient < 0, (extended.lhsCoefficient != 0) && (lhs < 0) != extended.iteration.isOdd) + XCTAssertEqual(extended.rhsCoefficient < 0, (extended.rhsCoefficient != 0) && (rhs < 0) == extended.iteration.isOdd) } } + + XCTAssertEqual(maxIteration, 10) } func testWrappingBézoutIdentityWorksForAllPairsOfUInt8() { self.continueAfterFailure = false + var maxIteration: UInt8 = 0000000 + for lhs in UInt8.min ... UInt8.max { for rhs in UInt8.min ... UInt8.max { let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) - if !extended.iterationIsOdd { + maxIteration = Swift.max(maxIteration, extended.iteration) + + if !extended.iteration.isOdd { XCTAssertEqual(extended.result, lhs &* extended.lhsCoefficient &- rhs &* extended.rhsCoefficient) } else { XCTAssertEqual(extended.result, rhs &* extended.rhsCoefficient &- lhs &* extended.lhsCoefficient) } } } + + XCTAssertEqual(maxIteration, 12) } } @@ -192,11 +205,14 @@ file: StaticString = #file, line: UInt = #line) { // MARK: + Algorithms //=----------------------------------------------------------------------------= -private func NBKAssertGreatestCommonDivisorByBinaryAlgorithm( +private func NBKAssertGreatestCommonDivisorByBinaryAlgorithm( _ lhs: T, _ rhs: T, _ result: T.Magnitude, _ overflow: Bool, file: StaticString = #file, line: UInt = #line) { //=------------------------------------------= - XCTAssertEqual(NBK.PBI.greatestCommonDivisorByBinaryAlgorithm(of: lhs, and: rhs), result, file: file, line: line) + let binary = NBK.PBI.greatestCommonDivisorByBinaryAlgorithm(of: lhs, and: rhs) + //=------------------------------------------= + XCTAssertEqual(binary, result, file: file, line: line) + XCTAssertEqual(T.isSigned && binary == T.min.magnitude, overflow, file: file, line: line) } private func NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsSigned( @@ -206,14 +222,14 @@ file: StaticString = #file, line: UInt = #line) { let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) //=------------------------------------------= XCTAssertEqual(extended.result, result, file: file, line: line) + XCTAssertEqual(extended.result == T.min.magnitude, overflow, file: file, line: line) XCTAssertEqual(lhs &* extended.lhsCoefficient &+ rhs &* extended.rhsCoefficient, T(bitPattern: result), file: file, line: line) - - if overflow { + + if result == T.min.magnitude { let values = [lhs, rhs] XCTAssert(1 <= values.filter({ $0 == T.min }).count, file: file, line: line) XCTAssert(2 == values.filter({ $0 == T.min }).count + values.filter(\.isZero).count, file: file, line: line) - XCTAssertEqual(T.min.magnitude, /*-----------------*/ extended.result, file: file, line: line) XCTAssertEqual(lhs == T.min && rhs != T.min ? -1 : 0, extended.lhsCoefficient, file: file, line: line) XCTAssertEqual(rhs == T.min /*-----------*/ ? -1 : 0, extended.rhsCoefficient, file: file, line: line) XCTAssertEqual(lhs == T.min /*-----------*/ ? -1 : 0, extended.lhsQuotient, file: file, line: line) @@ -253,7 +269,7 @@ file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(0, extended.rhsCoefficient, file: file, line: line) } - if !extended.iterationIsOdd { + if !extended.iteration.isOdd { XCTAssertEqual(result, lhs &* extended.lhsCoefficient &- rhs &* extended.rhsCoefficient, file: file, line: line) } else { XCTAssertEqual(result, rhs &* extended.rhsCoefficient &- lhs &* extended.lhsCoefficient, file: file, line: line)