diff --git a/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift index db2018d5..aea5b57f 100644 --- a/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift +++ b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift @@ -43,12 +43,30 @@ extension NBK.ProperBinaryInteger where Integer: NBKSignedInteger { /// /// It extends the Euclidean algorithm and returns some additional values. /// - /// ### Extension: Bézout's Identity + /// ### Result /// /// ```swift - /// let x = lhs * lhsCoefficient - /// let y = rhs * rhsCoefficient - /// precondition(result == x + y) + /// precondition(0 <= result && result <= T.min.magnitude) + /// ``` + /// + /// - Note: The GCD of `0` and `0` is `0`. + /// + /// - Note: The GCD of `Int.min` and `Int.min` is `Int.min.magnitude`. + /// + /// ### Bézout's identity + /// + /// ```swift + /// precondition(T(bitPattern: result) == lhs &* lhsCoefficient &+ rhs &* rhsCoefficient) + /// ``` + /// + /// ### Quotients of dividing by GCD + /// + /// ```swift + /// guard result != 00000 else { return } + /// guard result <= T.max else { return } + /// + /// precondition(lhsQuotient == lhs / T(result)) + /// precondition(rhsQuotient == rhs / T(result)) /// ``` /// @inlinable public static func greatestCommonDivisorByExtendedEuclideanAlgorithm(of lhs: Integer, and rhs: Integer) @@ -60,10 +78,10 @@ extension NBK.ProperBinaryInteger where Integer: NBKSignedInteger { let unsigned = Magnitude.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs.magnitude, and: rhs.magnitude) //=--------------------------------------= return (result: unsigned.result, - lhsCoefficient: Integer(sign: lhsIsLessThanZero == unsigned.even ? .minus : .plus, magnitude: unsigned.lhsCoefficient)!, - rhsCoefficient: Integer(sign: rhsIsLessThanZero != unsigned.even ? .minus : .plus, magnitude: unsigned.rhsCoefficient)!, - lhsQuotient: Integer(sign: lhsIsLessThanZero /*------------*/ ? .minus : .plus, magnitude: unsigned.lhsQuotient )!, - rhsQuotient: Integer(sign: rhsIsLessThanZero /*------------*/ ? .minus : .plus, magnitude: unsigned.rhsQuotient )!) + lhsCoefficient: Integer(sign: lhsIsLessThanZero != unsigned.isOddLoopCount ? .minus : .plus, magnitude: unsigned.lhsCoefficient)!, + rhsCoefficient: Integer(sign: rhsIsLessThanZero == unsigned.isOddLoopCount ? .minus : .plus, magnitude: unsigned.rhsCoefficient)!, + lhsQuotient: Integer(sign: lhsIsLessThanZero /*----------------------*/ ? .minus : .plus, magnitude: unsigned.lhsQuotient )!, + rhsQuotient: Integer(sign: rhsIsLessThanZero /*----------------------*/ ? .minus : .plus, magnitude: unsigned.rhsQuotient )!) } } @@ -115,25 +133,53 @@ extension NBK.ProperBinaryInteger where Integer: NBKUnsignedInteger { /// /// It extends the Euclidean algorithm and returns some additional values. /// - /// ### Extension: Bézout's Identity (unsigned) + /// ### Result /// /// ```swift - /// let x = lhs * lhsCoefficient - /// let y = rhs * rhsCoefficient - /// precondition(result == even ? x - y : y - x) + /// precondition(0 <= result && result <= T.max) /// ``` /// - @inlinable public static func greatestCommonDivisorByExtendedEuclideanAlgorithm(of lhs: Integer, and rhs: Integer) - -> (result: Integer, lhsCoefficient: Integer, rhsCoefficient: Integer, lhsQuotient: Integer, rhsQuotient: Integer, even: Bool) { + /// - Note: The GCD of `0` and `0` is `0`. + /// + /// ### Bézout's identity (unsigned) + /// + /// ```swift + /// var x = lhs &* lhsCoefficient + /// var y = rhs &* rhsCoefficient + /// + /// if isOddLoopCount { + /// swap(&x, &y) + /// } + /// + /// precondition(result == x &- y) + /// ``` + /// + /// ### Quotients of dividing by GCD + /// + /// ```swift + /// guard result != 0000000000 else { return } + /// precondition(lhsQuotient == lhs / result) + /// precondition(rhsQuotient == rhs / result) + /// ``` + /// + /// ### Loop count result + /// + /// ```swift + /// let lhsCoefficientSign = lhs.isLessThanZero != isOddLoopCount + /// let rhsCoefficientSign = rhs.isLessThanZero == isOddLoopCount + /// ``` + /// + @inlinable public static func greatestCommonDivisorByExtendedEuclideanAlgorithm(of lhs: Integer, and rhs: Integer) + -> (result: Integer, lhsCoefficient: Integer, rhsCoefficient: Integer, lhsQuotient: Integer, rhsQuotient: Integer, isOddLoopCount: Bool) { //=--------------------------------------= - var (r0, r1) = (lhs, rhs) as (Integer, Integer) - var (s0, s1) = (001, 000) as (Integer, Integer) - var (t0, t1) = (000, 001) as (Integer, Integer) - var even = ((((((((((((((((true)))))))))))))))) + var (r0, r1) = (lhs, rhs) as (Integer,Integer) + var (s0, s1) = (001, 000) as (Integer,Integer) + var (t0, t1) = (000, 001) as (Integer,Integer) + var isOddLoopCount = ((((((((((false)))))))))) //=--------------------------------------= while !r1.isZero { - - even.toggle() + + isOddLoopCount.toggle() let division = r0.quotientAndRemainder(dividingBy: r1) (r0, r1) = (r1, division.remainder) @@ -141,6 +187,6 @@ extension NBK.ProperBinaryInteger where Integer: NBKUnsignedInteger { (t0, t1) = (t1, division.quotient * t1 + t0) } //=--------------------------------------= - return (result: r0, lhsCoefficient: s0, rhsCoefficient: t0, lhsQuotient: t1, rhsQuotient: s1, even: even) + return (result: r0, lhsCoefficient: s0, rhsCoefficient: t0, lhsQuotient: t1, rhsQuotient: s1, isOddLoopCount: isOddLoopCount) } } diff --git a/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift index 430900a8..36d083da 100644 --- a/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift +++ b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift @@ -77,9 +77,8 @@ final class NBKProperBinaryIntegerTestsOnGreatestCommonDivisor: XCTestCase { NBKAssertGreatestCommonDivisorAsSigned(0 as Int, other, other.magnitude as UInt) } - NBKAssertGreatestCommonDivisorAsSigned( 0 as Int, Int.max, Int.max.magnitude as UInt) - NBKAssertGreatestCommonDivisorAsUnsigned(0 as UInt, Int.max.magnitude, Int.max.magnitude as UInt) - NBKAssertGreatestCommonDivisorAsUnsigned(0 as UInt, Int.min.magnitude, Int.min.magnitude as UInt) + NBKAssertGreatestCommonDivisorAsSigned(0 as Int, Int.max, Int.max.magnitude as UInt) + NBKAssertGreatestCommonDivisorAsSigned(0 as Int, Int.min, Int.min.magnitude as UInt, true) } //=------------------------------------------------------------------------= @@ -87,35 +86,57 @@ final class NBKProperBinaryIntegerTestsOnGreatestCommonDivisor: XCTestCase { //=------------------------------------------------------------------------= func testMinSignedIsSpecialBecauseTheGreatestCommonDivisorMayOverflow() { - func check(lhs: Int, rhs: Int, result: UInt, overflow: Bool = false) { - let binary = NBK.PBI.greatestCommonDivisorByBinaryAlgorithm(of: lhs, and: rhs) - XCTAssertEqual(binary, result) - - let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) - XCTAssertEqual(extended.result, result) - XCTAssertEqual(lhs &* extended.lhsCoefficient &+ rhs &* extended.rhsCoefficient, Int(bitPattern: result)) - - if let result = Int(exactly: result) { - XCTAssertFalse(overflow) - XCTAssertEqual(extended.lhsQuotient, lhs / Int(result)) - XCTAssertEqual(extended.rhsQuotient, rhs / Int(result)) - } else { - XCTAssertTrue (overflow) - XCTAssertEqual(extended.lhsQuotient, lhs >> Int.max) - XCTAssertEqual(extended.rhsQuotient, rhs >> Int.max) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, Int.min, Int.min.magnitude, true) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -Int.max, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -0000006, 00000000000000002) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -0000005, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -0000004, 00000000000000004) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -0000003, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -0000002, 00000000000000002) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, -0000001, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000000, Int.min.magnitude, true) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000001, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000002, 00000000000000002) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000003, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000004, 00000000000000004) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000005, 00000000000000001) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, 0000006, 00000000000000002) + NBKAssertGreatestCommonDivisorAsSigned(Int.min, Int.max, 00000000000000001) + } +} + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Greatest Common Divisor x E.E.A. +//*============================================================================* + +final class NBKProperBinaryIntegerTestsOnGreatestCommonDivisorByExtendedEuclideanAlgorithm: XCTestCase { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + func testWrappingBézoutIdentityWorksForAllPairsOfInt8() { + self.continueAfterFailure = false + for lhs in Int8.min ... Int8.max { + for rhs in Int8.min ... Int8.max { + let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) + let identity = lhs &* extended.lhsCoefficient &+ rhs &* extended.rhsCoefficient + XCTAssertEqual(identity, Int8(bitPattern: extended.result)) + } + } + } + + func testWrappingBézoutIdentityWorksForAllPairsOfUInt8() { + self.continueAfterFailure = false + for lhs in UInt8.min ... UInt8.max { + for rhs in UInt8.min ... UInt8.max { + let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) + let x = lhs &* extended.lhsCoefficient + let y = rhs &* extended.rhsCoefficient + let identity = extended.isOddLoopCount ? y &- x : x &- y + XCTAssertEqual(identity, extended.result) } } - - check(lhs: Int.min, rhs: Int.min, result: Int.min.magnitude, overflow: true) - check(lhs: Int.min, rhs: -Int.max, result: 00000000000000001) - check(lhs: Int.min, rhs: -0000003, result: 00000000000000001) - check(lhs: Int.min, rhs: -0000002, result: 00000000000000002) - check(lhs: Int.min, rhs: -0000001, result: 00000000000000001) - check(lhs: Int.min, rhs: 0000000, result: Int.min.magnitude, overflow: true) - check(lhs: Int.min, rhs: 0000001, result: 00000000000000001) - check(lhs: Int.min, rhs: 0000002, result: 00000000000000002) - check(lhs: Int.min, rhs: 0000003, result: 00000000000000001) - check(lhs: Int.min, rhs: Int.max, result: 00000000000000001) } } @@ -123,34 +144,44 @@ final class NBKProperBinaryIntegerTestsOnGreatestCommonDivisor: XCTestCase { // MARK: * NBK x Proper Binary Integer x Greatest Common Divisor x Assertions //*============================================================================* -private func NBKAssertGreatestCommonDivisorAsSigned( -_ lhs: T, _ rhs: T, _ gcd: T.Magnitude, +private func NBKAssertGreatestCommonDivisorAsSigned( +_ lhs: T, _ rhs: T, _ result: T.Magnitude, _ overflow: Bool = false, file: StaticString = #file, line: UInt = #line) { func with(_ lhs: T, _ rhs: T) { - NBKAssertGreatestCommonDivisorByBinaryAlgorithm(/*---------------*/lhs, rhs, gcd, file: file, line: line) - NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsSigned(lhs, rhs, gcd, file: file, line: line) - NBKAssertGreatestCommonDivisorAsUnsigned(lhs.magnitude, rhs.magnitude, /**/ gcd, file: file, line: line) + NBKAssertGreatestCommonDivisorByBinaryAlgorithm(/*---------------*/lhs, rhs, result, overflow, file: file, line: line) + NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsSigned(lhs, rhs, result, overflow, file: file, line: line) + NBKAssertGreatestCommonDivisorAsUnsigned(lhs.magnitude, rhs.magnitude, /**/ result, /*-----*/ file: file, line: line) } - with(0 + lhs, 0 + rhs) - with(0 + lhs, 0 - rhs) - with(0 - lhs, 0 + rhs) - with(0 - lhs, 0 - rhs) + brr: do { + with(0 + lhs, 0 + rhs) + with(0 + rhs, 0 + lhs) + } + + if rhs != T.min { + with(0 + lhs, 0 - rhs) + with(0 - rhs, 0 + lhs) + } - with(0 + rhs, 0 + lhs) - with(0 + rhs, 0 - lhs) - with(0 - rhs, 0 + lhs) - with(0 - rhs, 0 - lhs) + if lhs != T.min { + with(0 - lhs, 0 + rhs) + with(0 + rhs, 0 - lhs) + } + + if lhs != T.min, rhs != T.min { + with(0 - lhs, 0 - rhs) + with(0 - rhs, 0 - lhs) + } } -private func NBKAssertGreatestCommonDivisorAsUnsigned( -_ lhs: T, _ rhs: T, _ gcd: T.Magnitude, +private func NBKAssertGreatestCommonDivisorAsUnsigned( +_ lhs: T, _ rhs: T, _ result: T.Magnitude, file: StaticString = #file, line: UInt = #line) { func with(_ lhs: T, _ rhs: T) { - NBKAssertGreatestCommonDivisorByBinaryAlgorithm(lhs, rhs, gcd, file: file, line: line) - NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsUnsigned(lhs, rhs, gcd, file: file, line: line) + NBKAssertGreatestCommonDivisorByBinaryAlgorithm(lhs, rhs, result, false, file: file, line: line) + NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsUnsigned(lhs, rhs, result, file: file, line: line) } with(0 + lhs, 0 + rhs) @@ -162,46 +193,56 @@ file: StaticString = #file, line: UInt = #line) { //=----------------------------------------------------------------------------= private func NBKAssertGreatestCommonDivisorByBinaryAlgorithm( -_ lhs: T, _ rhs: T, _ gcd: T.Magnitude, +_ lhs: T, _ rhs: T, _ result: T.Magnitude, _ overflow: Bool, file: StaticString = #file, line: UInt = #line) { //=------------------------------------------= - XCTAssertEqual(NBK.PBI.greatestCommonDivisorByBinaryAlgorithm(of: lhs, and: rhs), gcd, file: file, line: line) + XCTAssertEqual(NBK.PBI.greatestCommonDivisorByBinaryAlgorithm(of: lhs, and: rhs), result, file: file, line: line) } -private func NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsSigned( -_ lhs: T, _ rhs: T, _ gcd: T.Magnitude, +private func NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsSigned( +_ lhs: T, _ rhs: T, _ result: T.Magnitude, _ overflow: Bool, file: StaticString = #file, line: UInt = #line) { //=------------------------------------------= let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) //=------------------------------------------= - XCTAssertEqual( (gcd), extended.result, file: file, line: line) - XCTAssertEqual(T(gcd), lhs * extended.lhsCoefficient + rhs * extended.rhsCoefficient, file: file, line: line) + XCTAssertEqual(extended.result, result, file: file, line: line) + XCTAssertEqual(lhs &* extended.lhsCoefficient &+ rhs &* extended.rhsCoefficient, T(bitPattern: result), file: file, line: line) - if !gcd.isZero { - XCTAssertEqual(extended.lhsQuotient, lhs / T(gcd), file: file, line: line) - XCTAssertEqual(extended.rhsQuotient, rhs / T(gcd), file: file, line: line) - } else { + if overflow { + 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) + XCTAssertEqual(rhs == T.min /*-----------*/ ? -1 : 0, extended.rhsQuotient, file: file, line: line) + } else if result.isZero { XCTAssertEqual(0, lhs, file: file, line: line) XCTAssertEqual(0, rhs, file: file, line: line) - XCTAssertEqual(1, extended.lhsQuotient, file: file, line: line) - XCTAssertEqual(0, extended.rhsQuotient, file: file, line: line) XCTAssertEqual(1, extended.lhsCoefficient, file: file, line: line) XCTAssertEqual(0, extended.rhsCoefficient, file: file, line: line) + XCTAssertEqual(1, extended.lhsQuotient, file: file, line: line) + XCTAssertEqual(0, extended.rhsQuotient, file: file, line: line) + } else { + XCTAssertEqual(extended.lhsQuotient, lhs / T(result), file: file, line: line) + XCTAssertEqual(extended.rhsQuotient, rhs / T(result), file: file, line: line) } } -private func NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsUnsigned( -_ lhs: T, _ rhs: T, _ gcd: T.Magnitude, +private func NBKAssertGreatestCommonDivisorByExtendedEuclideanAlgorithmAsUnsigned( +_ lhs: T, _ rhs: T, _ result: T.Magnitude, file: StaticString = #file, line: UInt = #line) { //=------------------------------------------= let extended = NBK.PBI.greatestCommonDivisorByExtendedEuclideanAlgorithm(of: lhs, and: rhs) //=------------------------------------------= - XCTAssertEqual(extended.result, T(gcd), file: file, line: line) + XCTAssertEqual(extended.result, T(result), file: file, line: line) - if !gcd.isZero { - XCTAssertEqual(extended.lhsQuotient, lhs / T(gcd), file: file, line: line) - XCTAssertEqual(extended.rhsQuotient, rhs / T(gcd), file: file, line: line) + if !result.isZero { + XCTAssertEqual(extended.lhsQuotient, lhs / T(result), file: file, line: line) + XCTAssertEqual(extended.rhsQuotient, rhs / T(result), file: file, line: line) } else { XCTAssertEqual(0, lhs, file: file, line: line) XCTAssertEqual(0, rhs, file: file, line: line) @@ -212,10 +253,11 @@ file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(0, extended.rhsCoefficient, file: file, line: line) } - let x = lhs * extended.lhsCoefficient - let y = rhs * extended.rhsCoefficient - let z = extended.even ? x - y : y - x - XCTAssertEqual(z, gcd, file: file, line: line) + if !extended.isOddLoopCount { + 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) + } } #endif