From 0f1737e77dd282e4c5474347bb1d8bb06e215759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Sun, 27 Oct 2024 06:42:41 +0100 Subject: [PATCH] [Tests] Rework of BinaryInteger+Factorization.swift (#108) (#110). --- .../CoreKit/BinaryInteger+Factorization.swift | 6 + Sources/CoreKit/Models/Bezout.swift | 1 + Sources/TestKit/Test+Factorization.swift | 2 +- .../DoubleInt+Factorization.swift | 46 -- .../InfiniInt+Factorization.swift | 86 --- .../BinaryInteger+Factorization.swift | 503 +++++++++++------- 6 files changed, 316 insertions(+), 328 deletions(-) delete mode 100644 Tests/DoubleIntKitTests/DoubleInt+Factorization.swift delete mode 100644 Tests/InfiniIntKitTests/InfiniInt+Factorization.swift diff --git a/Sources/CoreKit/BinaryInteger+Factorization.swift b/Sources/CoreKit/BinaryInteger+Factorization.swift index 84471194..b1561532 100644 --- a/Sources/CoreKit/BinaryInteger+Factorization.swift +++ b/Sources/CoreKit/BinaryInteger+Factorization.swift @@ -27,6 +27,7 @@ extension BinaryInteger { /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) + /// 5. gcd(a, ∞) == nil /// /// ### Gretest Common Divisor to Least Common Multiple /// @@ -53,6 +54,7 @@ extension BinaryInteger { /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) + /// 5. gcd(a, ∞) == nil /// /// ### Bézout's identity /// @@ -90,6 +92,7 @@ extension FiniteInteger { /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) + /// 5. gcd(a, ∞) == nil /// /// ### Gretest Common Divisor to Least Common Multiple /// @@ -113,6 +116,7 @@ extension FiniteInteger { /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) + /// 5. gcd(a, ∞) == nil /// /// ### Bézout's identity /// @@ -147,6 +151,7 @@ extension Finite where Value: BinaryInteger { /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) + /// 5. gcd(a, ∞) == nil /// /// ### Gretest Common Divisor to Least Common Multiple /// @@ -184,6 +189,7 @@ extension Finite where Value: BinaryInteger { /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) + /// 5. gcd(a, ∞) == nil /// /// ### Bézout's identity /// diff --git a/Sources/CoreKit/Models/Bezout.swift b/Sources/CoreKit/Models/Bezout.swift index 6b9fe3c1..ef7157d1 100644 --- a/Sources/CoreKit/Models/Bezout.swift +++ b/Sources/CoreKit/Models/Bezout.swift @@ -21,6 +21,7 @@ /// 2. gcd(a, b) == gcd(±a, ±b) /// 3. gcd(a, b) == gcd( a, b % a) /// 4. gcd(a, b) == gcd( b, a) +/// 5. gcd(a, ∞) == nil /// /// ### Bézout's identity /// diff --git a/Sources/TestKit/Test+Factorization.swift b/Sources/TestKit/Test+Factorization.swift index d0e72a42..38198319 100644 --- a/Sources/TestKit/Test+Factorization.swift +++ b/Sources/TestKit/Test+Factorization.swift @@ -14,7 +14,7 @@ import CoreKit //*============================================================================* extension Test { - + #warning("test these things in new tests") //=------------------------------------------------------------------------= // MARK: Utilities //=------------------------------------------------------------------------= diff --git a/Tests/DoubleIntKitTests/DoubleInt+Factorization.swift b/Tests/DoubleIntKitTests/DoubleInt+Factorization.swift deleted file mode 100644 index e8cee3f3..00000000 --- a/Tests/DoubleIntKitTests/DoubleInt+Factorization.swift +++ /dev/null @@ -1,46 +0,0 @@ -//=----------------------------------------------------------------------------= -// This source file is part of the Ultimathnum open source project. -// -// Copyright (c) 2023 Oscar Byström Ericsson -// Licensed under Apache License, Version 2.0 -// -// See http://www.apache.org/licenses/LICENSE-2.0 for license information. -//=----------------------------------------------------------------------------= - -import CoreKit -import DoubleIntKit -import TestKit - -//*============================================================================* -// MARK: * Double Int x Factorization -//*============================================================================* - -extension DoubleIntTests { - - //=------------------------------------------------------------------------= - // MARK: Tests - //=------------------------------------------------------------------------= - - func testGreatestCommonDivisorOfSmallPrimeComposites() { - func whereIs(_ type: T.Type) where T: SystemsInteger { - //=----------------------------------= - let primes54 = primes54.map(T.init(_:)) - //=----------------------------------= - func check(_ test: Test, lhs a: Range, rhs b: Range, gcd c: Range) { - let lhs = primes54[a].reduce(1, *) - let rhs = primes54[b].reduce(1, *) - let gcd = primes54[c].reduce(1, *).magnitude() - test.euclidean(lhs, rhs, gcd) - } - //=----------------------------------= - check(Test(), lhs: 00 ..< 27, rhs: 27 ..< 54, gcd: 27 ..< 27) - check(Test(), lhs: 00 ..< 30, rhs: 20 ..< 50, gcd: 20 ..< 30) - check(Test(), lhs: 11 ..< 31, rhs: 21 ..< 41, gcd: 21 ..< 31) - check(Test(), lhs: 12 ..< 32, rhs: 22 ..< 42, gcd: 22 ..< 32) - check(Test(), lhs: 13 ..< 33, rhs: 23 ..< 43, gcd: 23 ..< 33) - } - - whereIs(I256.self) - whereIs(U256.self) - } -} diff --git a/Tests/InfiniIntKitTests/InfiniInt+Factorization.swift b/Tests/InfiniIntKitTests/InfiniInt+Factorization.swift deleted file mode 100644 index db9b6e24..00000000 --- a/Tests/InfiniIntKitTests/InfiniInt+Factorization.swift +++ /dev/null @@ -1,86 +0,0 @@ -//=----------------------------------------------------------------------------= -// This source file is part of the Ultimathnum open source project. -// -// Copyright (c) 2023 Oscar Byström Ericsson -// Licensed under Apache License, Version 2.0 -// -// See http://www.apache.org/licenses/LICENSE-2.0 for license information. -//=----------------------------------------------------------------------------= - -import CoreKit -import InfiniIntKit -import TestKit - -//*============================================================================* -// MARK: * Infini Int x Factorization -//*============================================================================* - -extension InfiniIntTests { - - //=------------------------------------------------------------------------= - // MARK: Tests - //=------------------------------------------------------------------------= - - func testGreatestCommonDivisorOfSmallPrimeComposites() { - func whereIs(_ type: T.Type) where T: ArbitraryInteger { - //=----------------------------------= - let primes54 = primes54.map(T.init(_:)) - let result54 = primes54.reduce(1, *) - //=----------------------------------= - func check(_ test: Test, lhs a: Range, rhs b: Range, gcd c: Range) { - let lhs = primes54[a].reduce(1, *) - let rhs = primes54[b].reduce(1, *) - let gcd = primes54[c].reduce(1, *).magnitude() - test.euclidean(lhs, rhs, gcd) - test.euclidean( - lhs.squared().unwrap(), - rhs.squared().unwrap(), - gcd.squared().unwrap() - ) - } - //=----------------------------------= - check(Test(), lhs: 00 ..< 27, rhs: 27 ..< 54, gcd: 27 ..< 27) - check(Test(), lhs: 00 ..< 30, rhs: 20 ..< 50, gcd: 20 ..< 30) - check(Test(), lhs: 11 ..< 31, rhs: 21 ..< 41, gcd: 21 ..< 31) - check(Test(), lhs: 12 ..< 32, rhs: 22 ..< 42, gcd: 22 ..< 32) - check(Test(), lhs: 13 ..< 33, rhs: 23 ..< 43, gcd: 23 ..< 33) - //=----------------------------------= - Test().euclidean(result54, result54, T.Magnitude(result54)) - } - - whereIs(InfiniInt.self) - whereIs(InfiniInt.self) - #if !DEBUG - whereIs(InfiniInt.self) - whereIs(InfiniInt.self) - #endif - } - - func testGreatestCommonDivisorOfInfiniteInputsIsInvalid() { - func whereIs(_ type: T.Type) where T: ArbitraryInteger { - Test().euclidean(~1 as T, ~1 as T, T.isSigned ? 2 : nil) - Test().euclidean(~1 as T, ~0 as T, T.isSigned ? 1 : nil) - Test().euclidean(~1 as T, 0 as T, T.isSigned ? 2 : nil) - Test().euclidean(~1 as T, 1 as T, T.isSigned ? 1 : nil) - - Test().euclidean(~0 as T, ~1 as T, T.isSigned ? 1 : nil) - Test().euclidean(~0 as T, ~0 as T, T.isSigned ? 1 : nil) - Test().euclidean(~0 as T, 0 as T, T.isSigned ? 1 : nil) - Test().euclidean(~0 as T, 1 as T, T.isSigned ? 1 : nil) - - Test().euclidean( 0 as T, ~1 as T, T.isSigned ? 2 : nil) - Test().euclidean( 0 as T, ~0 as T, T.isSigned ? 1 : nil) - Test().euclidean( 0 as T, 0 as T, 0) - Test().euclidean( 0 as T, 1 as T, 1) - - Test().euclidean( 1 as T, ~1 as T, T.isSigned ? 1 : nil) - Test().euclidean( 1 as T, ~0 as T, T.isSigned ? 1 : nil) - Test().euclidean( 1 as T, 0 as T, 1) - Test().euclidean( 1 as T, 1 as T, 1) - } - - for type in Self.types { - whereIs(type) - } - } -} diff --git a/Tests/UltimathnumTests/BinaryInteger+Factorization.swift b/Tests/UltimathnumTests/BinaryInteger+Factorization.swift index f06a352e..13511bce 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Factorization.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Factorization.swift @@ -8,250 +8,363 @@ //=----------------------------------------------------------------------------= import CoreKit -import DoubleIntKit +import RandomIntKit import InfiniIntKit -import TestKit +import TestKit2 //*============================================================================* // MARK: * Binary Integer x Factorization //*============================================================================* -final class BinaryIntegerTestsOnFactorization: XCTestCase { +@Suite struct BinaryIntegerTestsOnFactorization { //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------= - func testEachPairInFibonacciSequenceIsCoprime() { - var fibonacci = (low: 0 as IX, high: 1 as IX) - next: while fibonacci.high >= fibonacci.low { - Test().euclidean(fibonacci.low, fibonacci.high, 00000001 as UX) - ((fibonacci)) = (fibonacci.low &+ fibonacci.high, fibonacci.high) + /// - Note: This is just a fun fact. + @Test("BinaryInteger/factorization: GCD(fib(x), fib(x+1)) == 1") + func fibonacciSequencePairsAreCoprime() throws { + var low = U64(0) + var high = U64(1) + + while let next = low.plus(high).optional() { + try #require(low.euclidean(high) == 1) + low = high + high = next } + + try #require(low == 07540113804746346429) + try #require(high == 12200160415121876738) } - func testEveryIntegerDividesZero() { - func whereIs(_ type: T.Type) where T: BinaryInteger { - let patterns: Range = -5..<5 - - for x: T in patterns.lazy.map(T.init(load:)) { - Test().euclidean(T.zero, x, x.isInfinite ? nil : x.magnitude()) + /// Checks that our small prime collection consists of primes. + @Test("BinaryInteger/factorization: GCD(prime, 2 ..< prime) == 1") + func greatestCommonDivisorForEachSmallPrimeFromZeroThroughItself() throws { + try withOnlyOneCallToRequire { require in + for prime in primes54 { + require(prime.euclidean(00000) == prime) + require(prime.euclidean(00001) == 00001) + require(prime.euclidean(prime) == prime) + + for other in 2 ..< prime { + require(prime.euclidean(other) == 1) + require(prime.remainder(other) != 0) + } } - - Test().euclidean(T.zero, Esque.min, Esque.min.magnitude()) - Test().euclidean(T.zero, Esque.max, T.isFinite ? Esque.max.magnitude() : nil) } + } + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/factorization: find (x, y) in GCD(a, b) == a * x + b * y", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func bezout(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - for type in typesAsBinaryInteger { - whereIs(type) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 32 { + let lhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let bezout = try #require(lhs.bezout(rhs)) + + try #require(bezout.divisor == lhs.euclidean(rhs), "GCD") + + always: do { + let a = IXL(lhs) * IXL(bezout.lhsCoefficient) + let b = IXL(rhs) * IXL(bezout.rhsCoefficient) + try #require(a + b == bezout.divisor, "bézout identity") + } + } } } - func testGreatestCommonDivisorOfSmallPowersOf2() { - func whereIs(_ type: T.Type) where T: BinaryInteger { - Test().euclidean( 2 as T, 16 as T, 2 as T.Magnitude) - Test().euclidean( 4 as T, 16 as T, 4 as T.Magnitude) - Test().euclidean( 8 as T, 16 as T, 8 as T.Magnitude) - Test().euclidean(16 as T, 16 as T, 16 as T.Magnitude) + @Test( + "BinaryInteger/factorization: GCD(a, b) vs prime multiplication", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorVersusPrimeMultiplication(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + let primes = primes54.compactMap{T.exactly($0).optional()} + for _ in 0 ..< conditional(debug: 16, release: 64) { + var lhs: T = 1 + var rhs: T = 1 + var divisor: T.Magnitude = 1 + var lhsCount = Array(repeating: UX.zero, count: primes.count) + var rhsCount = Array(repeating: UX.zero, count: primes.count) + + for _ in 0 ..< Swift.Int(IX.random(in: 2...32, using: &randomness)) { + let index = (primes).indices.randomElement(using: &randomness.stdlib)! + let lhsInsert = Bool.random(using: &randomness.stdlib) + let rhsInsert = Bool.random(using: &randomness.stdlib) + + if (lhsInsert), let next = lhs.times(primes[index]).optional() { + lhs = next; lhsCount[index] += 1 + } + + if (rhsInsert), let next = rhs.times(primes[index]).optional() { + rhs = next; rhsCount[index] += 1 + } + } + + if T.isSigned, Bool.random(using: &randomness.stdlib) { + try #require(!lhs.negate().error) + } + + if T.isSigned, Bool.random(using: &randomness.stdlib) { + try #require(!rhs.negate().error) + } + + for index in primes.indices { + let exponent = Swift.min(lhsCount[index], rhsCount[index]) + if !exponent.isZero { + let base: T.Magnitude = primes[index].magnitude() + let power = base.power(exponent, coefficient: divisor) + divisor = try #require(power.optional()) + } + } + + try #require(divisor == lhs.euclidean(rhs)) + try #require(divisor == lhs.bezout(rhs)?.divisor) + } } + } +} + +//*============================================================================* +// MARK: * Binary Integer x Factorization x Edge Cases +//*============================================================================* + +@Suite struct BinaryIntegerTestsOnFactorizationEdgeCases { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/factorization/edge-cases: GCD(x, ∞) == nil", + Tag.List.tags(.generic, .random), + arguments: typesAsArbitraryIntegerAsUnsigned, fuzzers + ) func greatestCommonDivisorOfInfiniteIsNil(type: any ArbitraryIntegerAsUnsigned.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - for type in typesAsBinaryInteger { - whereIs(type) + func whereIs(_ type: T.Type) throws where T: ArbitraryIntegerAsUnsigned { + for _ in 0 ..< 32 { + let a = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let b = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness).toggled() + + try #require(a.isInfinite == false) + try #require(b.isInfinite) + + try #require(a.bezout (b) == nil) + try #require(b.bezout (a) == nil) + try #require(b.bezout (b) == nil) + try #require(a.euclidean(b) == nil) + try #require(b.euclidean(a) == nil) + try #require(b.euclidean(b) == nil) + } } } - func testGreatestCommonDivisorOfSmallPrimeProducts() { - func whereIs(_ type: T.Type) where T: BinaryInteger { - precondition(T.size >= Count(16)) - // even x even - Test().euclidean(2 as T, 2 * 5 * 11 as T, 2 as T.Magnitude) - Test().euclidean(2 * 3 as T, 2 * 5 * 11 as T, 2 as T.Magnitude) - Test().euclidean(2 * 3 * 5 as T, 2 * 5 * 11 as T, 2 * 5 as T.Magnitude) - Test().euclidean(2 * 3 * 5 * 7 as T, 2 * 5 * 11 as T, 2 * 5 as T.Magnitude) - Test().euclidean(2 * 3 * 5 * 7 * 11 as T, 2 * 5 * 11 as T, 2 * 5 * 11 as T.Magnitude) - Test().euclidean( 3 * 5 * 7 * 11 as T, 2 * 5 * 11 as T, 5 * 11 as T.Magnitude) - Test().euclidean( 5 * 7 * 11 as T, 2 * 5 * 11 as T, 5 * 11 as T.Magnitude) - Test().euclidean( 7 * 11 as T, 2 * 5 * 11 as T, 11 as T.Magnitude) - Test().euclidean( 11 as T, 2 * 5 * 11 as T, 11 as T.Magnitude) - // even x odd - Test().euclidean(2 as T, 3 * 5 * 07 as T, 1 as T.Magnitude) - Test().euclidean(2 * 3 as T, 3 * 5 * 07 as T, 3 as T.Magnitude) - Test().euclidean(2 * 3 * 5 as T, 3 * 5 * 07 as T, 3 * 5 as T.Magnitude) - Test().euclidean(2 * 3 * 5 * 7 as T, 3 * 5 * 07 as T, 3 * 5 * 07 as T.Magnitude) - Test().euclidean(2 * 3 * 5 * 7 * 11 as T, 3 * 5 * 07 as T, 3 * 5 * 07 as T.Magnitude) - Test().euclidean( 3 * 5 * 7 * 11 as T, 3 * 5 * 07 as T, 3 * 5 * 07 as T.Magnitude) - Test().euclidean( 5 * 7 * 11 as T, 3 * 5 * 07 as T, 5 * 07 as T.Magnitude) - Test().euclidean( 7 * 11 as T, 3 * 5 * 07 as T, 07 as T.Magnitude) - Test().euclidean( 11 as T, 3 * 5 * 07 as T, 01 as T.Magnitude) - // odd x even - Test().euclidean(1 as T, 2 * 5 * 11 as T, 1 as T.Magnitude) - Test().euclidean(1 * 3 as T, 2 * 5 * 11 as T, 1 as T.Magnitude) - Test().euclidean(1 * 3 * 5 as T, 2 * 5 * 11 as T, 1 * 5 as T.Magnitude) - Test().euclidean(1 * 3 * 5 * 7 as T, 2 * 5 * 11 as T, 1 * 5 as T.Magnitude) - Test().euclidean(1 * 3 * 5 * 7 * 11 as T, 2 * 5 * 11 as T, 1 * 5 * 11 as T.Magnitude) - Test().euclidean( 3 * 5 * 7 * 11 as T, 2 * 5 * 11 as T, 5 * 11 as T.Magnitude) - Test().euclidean( 5 * 7 * 11 as T, 2 * 5 * 11 as T, 5 * 11 as T.Magnitude) - Test().euclidean( 7 * 11 as T, 2 * 5 * 11 as T, 11 as T.Magnitude) - Test().euclidean( 11 as T, 2 * 5 * 11 as T, 11 as T.Magnitude) - // odd x odd - Test().euclidean(1 as T, 3 * 5 * 07 as T, 1 as T.Magnitude) - Test().euclidean(1 * 3 as T, 3 * 5 * 07 as T, 3 as T.Magnitude) - Test().euclidean(1 * 3 * 5 as T, 3 * 5 * 07 as T, 3 * 5 as T.Magnitude) - Test().euclidean(1 * 3 * 5 * 7 as T, 3 * 5 * 07 as T, 3 * 5 * 07 as T.Magnitude) - Test().euclidean(1 * 3 * 5 * 7 * 11 as T, 3 * 5 * 07 as T, 3 * 5 * 07 as T.Magnitude) - Test().euclidean( 3 * 5 * 7 * 11 as T, 3 * 5 * 07 as T, 3 * 5 * 07 as T.Magnitude) - Test().euclidean( 5 * 7 * 11 as T, 3 * 5 * 07 as T, 5 * 07 as T.Magnitude) - Test().euclidean( 7 * 11 as T, 3 * 5 * 07 as T, 07 as T.Magnitude) - Test().euclidean( 11 as T, 3 * 5 * 07 as T, 01 as T.Magnitude) + @Test( + "BinaryInteger/factorization/edge-cases: GCD(x, x) == |x|", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorOfDuplicateIsMagnitude(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 32 { + let random = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let expectation = random.magnitude() + + try #require(expectation == random.euclidean(random)) + try #require(expectation == random.bezout(T.zero)?.divisor) + } } + } + + @Test( + "BinaryInteger/factorization/edge-cases: GCD(x, 0) == |x|", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorOfZeroAndOtherIsMagnitudeOfOther(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - whereIs(I32.self) - whereIs(U32.self) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 32 { + let random = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let expectation = random.magnitude() + + try #require(expectation == random.euclidean(T.zero)) + try #require(expectation == T.zero.euclidean(random)) + + try #require(expectation == random.bezout(T.zero)?.divisor) + try #require(expectation == T.zero.bezout(random)?.divisor) + } + } + } + + @Test( + "BinaryInteger/factorization/edge-cases: GCD(x, 1) == 1", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorOfOneAndOtherIsOne(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - whereIs(DoubleInt.self) - whereIs(DoubleInt.self) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 32 { + let random = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let expectation = T.Magnitude.lsb + + try #require(expectation == random.euclidean(T.lsb)) + try #require(expectation == T.lsb.euclidean(random)) + + try #require(expectation == random.bezout(T.lsb)?.divisor) + try #require(expectation == T.lsb.bezout(random)?.divisor) + } + } + } + + @Test( + "BinaryInteger/factorization/edge-cases: GCD(a, b) == GCD(b, a)", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorIsCommutative(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - whereIs(InfiniInt.self) - whereIs(InfiniInt.self) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 32 { + let lhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + + try #require(lhs.euclidean(rhs) == rhs.euclidean(lhs)) + try #require(lhs.bezout(rhs)?.divisor == rhs.bezout(lhs)?.divisor) + } + } } + @Test( + "BinaryInteger/factorization/edge-cases: GCD(a, b) == GCD(±a, ±b)", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryIntegerAsSigned, fuzzers + ) func greatestCommonDivisorIsSignAgnostic(type: any SignedInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: SignedInteger { + for _ in 0 ..< 32 { + let lhs = T.entropic(through: Shift.max(or: 127), using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), using: &randomness) + let expectation: T.Magnitude? = lhs.magnitude().euclidean(rhs.magnitude()) + try #require(expectation == lhs.euclidean(rhs)) + try #require(expectation == lhs.bezout(rhs).divisor) + } + } + } + + @Test( + "BinaryInteger/factorization/edge-cases: GCD(a, b) == GCD(a, b % a)", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorRemainderInvariant(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 8 { + let lhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + + if let remainder: T = rhs.remainder(lhs) { + let expectation: T.Magnitude? = lhs.euclidean(rhs) + try #require(expectation == lhs.euclidean(remainder)) + try #require(expectation == lhs.bezout(remainder)?.divisor) + } + } + } + } +} + +//*============================================================================* +// MARK: * Binary Integer x Factorization x Conveniences +//*============================================================================* + +@Suite(.tags(.forwarding)) struct BinaryIntegerTestsOnFactorizationConveniences { + //=------------------------------------------------------------------------= - // MARK: Tests x Each Byte + // MARK: Tests x Finite //=------------------------------------------------------------------------= - func testCoefficientsForEachBytePairAsI8() throws { - #if DEBUG - throw XCTSkip("req. release mode") - #else - var lhsCoefficients: Set = [] - var rhsCoefficients: Set = [] + @Test( + "BinaryInteger/factorization/edge-cases: bezout(_:) as Finite", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func bezoutAsFinite(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - for a in I8.min...I8.max { - for b in I8.min...I8.max { - let bezout = a.bezout(b) - lhsCoefficients.insert(bezout.lhsCoefficient) - rhsCoefficients.insert(bezout.rhsCoefficient) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 4 { + let lhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let expectation: Optional> = lhs.bezout(rhs) + try #require(expectation == Finite(lhs).bezout(Finite(rhs)) as Bezout) } } - - Test().yay(lhsCoefficients.sorted().elementsEqual(-63...63)) - Test().yay(rhsCoefficients.sorted().elementsEqual(-63...63)) - #endif } - func testCoefficientsForEachBytePairAsU8() throws { - #if DEBUG - throw XCTSkip("req. release mode") - #else - var lhsCoefficients: Set = [] - var rhsCoefficients: Set = [] + @Test( + "BinaryInteger/factorization/edge-cases: bezout(_:) as FiniteInteger", + Tag.List.tags(.generic, .random), + arguments: typesAsFiniteInteger, fuzzers + ) func bezoutAsFiniteInteger(type: any FiniteInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - for a in U8.min...U8.max { - for b in U8.min...U8.max { - let bezout = a.bezout(b) - lhsCoefficients.insert(bezout.lhsCoefficient) - rhsCoefficients.insert(bezout.rhsCoefficient) + func whereIs(_ type: T.Type) throws where T: FiniteInteger { + for _ in 0 ..< 4 { + let lhs = T.entropic(through: Shift.max(or: 127), using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), using: &randomness) + let expectation: Optional> = lhs.bezout(rhs) + try #require(expectation == lhs.bezout(rhs) as Bezout) } } - - Test().yay(lhsCoefficients.sorted().elementsEqual(-127...127)) - Test().yay(rhsCoefficients.sorted().elementsEqual(-127...127)) - #endif } - func testGreatestCommonDivisorForEachBytePairAsSigned() { - func whereIs(_ type: T.Type) where T: SignedInteger { - //=----------------------------------= - var success = U32.zero - let values8 = T(I8.min) ... T(I8.max) - //=----------------------------------= - for lhs in values8 { - for rhs in values8 { - let bezout = lhs.bezout(rhs) - let euclidean = lhs.euclidean(rhs) - - if euclidean == bezout.divisor { - success &+= 1 - } - - if euclidean == lhs.magnitude().euclidean(rhs.magnitude()) { - success &+= 1 // proxy validation through unsigned types - } - - if T(raw: euclidean) == lhs &* bezout.lhsCoefficient &+ rhs &* bezout.rhsCoefficient { - success &+= 1 - } - } + @Test( + "BinaryInteger/factorization/edge-cases: euclidean(_:) as Finite", + Tag.List.tags(.generic, .random), + arguments: typesAsBinaryInteger, fuzzers + ) func greatestCommonDivisorAsFinite(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< 4 { + let lhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), as: Domain.finite, using: &randomness) + let expectation: Optional = lhs.euclidean(rhs) + try #require(expectation == Finite(lhs).euclidean(Finite(rhs)) as T.Magnitude) } - - Test().same(success, 3 &* 65536) } - - whereIs(I8 .self) - #if !DEBUG - whereIs(I16.self) - whereIs(I32.self) - whereIs(I64.self) - whereIs(IX .self) - whereIs(DoubleInt.self) - whereIs(InfiniInt.self) - #endif } - func testGreatestCommonDivisorForEachBytePairAsUnsigned() { - func whereIs(_ type: T.Type) where T: UnsignedInteger { - //=----------------------------------= - var coprime = U32.zero - var success = U32.zero - let values8 = T(U8.min) ... T(U8.max) - //=----------------------------------= - for lhs in values8 { - for rhs in values8 { - let bezout = lhs.bezout(rhs)! - let euclidean = lhs.euclidean(rhs)! - - if euclidean == 1 { - coprime &+= 1 - } - - always: do { - let a = I16(lhs) * I16(bezout.lhsCoefficient) - let b = I16(rhs) * I16(bezout.rhsCoefficient) - success &+= U32(Bit((a + b) == bezout.divisor)) - } - - if euclidean == bezout.divisor { - success &+= 1 - } - - if lhs.isZero, rhs.isZero { - success &+= U32(Bit(euclidean == 0)) - } else { - success &+= U32(Bit(euclidean >= 1)) - } - - if bezout == lhs.bezout(rhs) { - success &+= 1 - } - - if euclidean == lhs.euclidean(rhs) { - success &+= 1 - } - } + @Test( + "BinaryInteger/factorization/edge-cases: euclidean(_:) as FiniteInteger", + Tag.List.tags(.generic, .random), + arguments: typesAsFiniteInteger, fuzzers + ) func greatestCommonDivisorAsFiniteInteger(type: any FiniteInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: FiniteInteger { + for _ in 0 ..< 4 { + let lhs = T.entropic(through: Shift.max(or: 127), using: &randomness) + let rhs = T.entropic(through: Shift.max(or: 127), using: &randomness) + let expectation: Optional = lhs.euclidean(rhs) + try #require(expectation == lhs.euclidean(rhs) as T.Magnitude) } - - Test().same(coprime, 1 &* 39641) - Test().same(success, 5 &* 65536) } - - whereIs(U8 .self) - #if !DEBUG - whereIs(U16.self) - whereIs(U32.self) - whereIs(U64.self) - whereIs(UX .self) - whereIs(DoubleInt.self) - whereIs(InfiniInt.self) - #endif } }