diff --git a/Sources/CoreKit/BinaryInteger+Exponentiation.swift b/Sources/CoreKit/BinaryInteger+Exponentiation.swift index cc659868..f3bd1e34 100644 --- a/Sources/CoreKit/BinaryInteger+Exponentiation.swift +++ b/Sources/CoreKit/BinaryInteger+Exponentiation.swift @@ -19,6 +19,10 @@ extension BinaryInteger { // MARK: Transformations //=------------------------------------------------------------------------= + /// A square-and-multiply exponentiation algorithm. + /// + /// - Note: All operations on `base` are `borrowing`. + /// /// ### Development /// /// Use `1` systems integer `exponent` type per type of `Self`. @@ -27,44 +31,36 @@ extension BinaryInteger { /// Magnitude.size ≤ IX.max.: Magnitude /// Magnitude.size > IX.max.: UX /// - /// - Note: The nonzero `coefficient` simplifies `error` reporting. + /// - Todo: Small arbitrary integer optimization (#44) (#153). /// - @inlinable internal static func resolve( - _ base: borrowing Self, - power exponent: borrowing Natural, - coefficient: borrowing Nonzero + @inlinable internal static func perform( + _ base: consuming Self, + power exponent: consuming Natural ) -> Fallible { var error: Bool = false - var power: Self = (copy coefficient).value - var multiplier: Self = copy base - var exponent: some UnsignedInteger = (copy exponent).value + var power: Self = Self(load: Element.lsb) exponentiation: while true { - if Bool(exponent.lsb) { - power = power.times(multiplier).sink(&error) + if Bool(exponent.value.lsb) { + power = power.times(base).sink(&error) } - exponent = exponent.down(Shift.one) + exponent = Natural(unchecked: exponent.value.down(Shift.one)) - if exponent.isZero { + if exponent.value.isZero { return (power).veto(error) } - multiplier = multiplier.squared().sink(&error) + base = base.squared().sink(&error) } } @inlinable internal static func resolve( - _ base: borrowing Self, - power exponent: borrowing some UnsignedInteger, - coefficient: borrowing Self + _ base: borrowing Self, + power exponent: borrowing some UnsignedInteger ) -> Fallible { - guard let coefficient = Nonzero(exactly: copy coefficient) else { - return Fallible(Self.zero) - } - if !Self.isArbitrary { var (magic, error) = Magnitude.exactly(exponent).components() if (error) { @@ -74,11 +70,11 @@ extension BinaryInteger { } } - return Self.resolve(base, power: Natural(unchecked: magic), coefficient: coefficient).veto(error) + return Self.perform(copy base, power: Natural(unchecked: magic)).veto(error) } else { var (magic) = UX(clamping:(((exponent)))) // the allocation limit is IX.max (((((magic)))))[Shift.min] = exponent.lsb // preserves the lsb to toggle ~0 - return Self.resolve(base, power: Natural(unchecked: magic), coefficient: coefficient) + return Self.perform(copy base, power: Natural(unchecked: magic)) } } @@ -86,9 +82,7 @@ extension BinaryInteger { // MARK: Transformations //=------------------------------------------------------------------------= - /// Returns a `power` and an `error` indiactor. - /// - /// - Returns: `pow(self, exponent) * coefficient` + /// Returns `self` to the power of `exponent` and an `error` indiactor. /// /// ```swift /// I8(0).power(U8(1), coefficient: I8(2)) // I8.exactly( 0) @@ -99,20 +93,13 @@ extension BinaryInteger { /// /// ### Exponentiation /// - /// - Note: The default `coefficient` is `1`. - /// /// - Note: The `error` is set if the operation is `lossy`. /// - @inlinable public borrowing func power( - _ exponent: borrowing Magnitude, - coefficient: borrowing Self = 1 - ) -> Fallible { - Self.resolve(self, power: exponent, coefficient: coefficient) + @inlinable public borrowing func power(_ exponent: borrowing Magnitude) -> Fallible { + Self.resolve(self, power: exponent) } - /// Returns a `power` and an `error` indiactor. - /// - /// - Returns: `pow(self, exponent) * coefficient` + /// Returns `self` to the power of `exponent` and an `error` indiactor. /// /// ```swift /// I8(0).power(U8(1), coefficient: I8(2)) // I8.exactly( 0) @@ -123,15 +110,10 @@ extension BinaryInteger { /// /// ### Exponentiation /// - /// - Note: The default `coefficient` is `1`. - /// /// - Note: The `error` is set if the operation is `lossy`. /// - @inlinable public borrowing func power( - _ exponent: borrowing some UnsignedInteger, - coefficient: borrowing Self = 1 - ) -> Fallible { - Self.resolve(self, power: exponent, coefficient: coefficient) + @inlinable public borrowing func power(_ exponent: borrowing some UnsignedInteger) -> Fallible { + Self.resolve(self, power: exponent) } } @@ -145,9 +127,7 @@ extension BinaryInteger where Self: ArbitraryInteger & SignedInteger { // MARK: Transformations //=------------------------------------------------------------------------= - /// Returns a `power`. - /// - /// - Returns: `pow(self, exponent) * coefficient` + /// Returns `self` to the power of `exponent`. /// /// ```swift /// I8(0).power(U8(1), coefficient: I8(2)) // I8.exactly( 0) @@ -158,20 +138,13 @@ extension BinaryInteger where Self: ArbitraryInteger & SignedInteger { /// /// ### Exponentiation /// - /// - Note: The default `coefficient` is `1`. - /// /// - Note: The `error` is set if the operation is `lossy`. /// - @inlinable public borrowing func power( - _ exponent: borrowing Magnitude, - coefficient: borrowing Self = 1 - ) -> Self { - self.power(exponent, coefficient: coefficient).unchecked() + @inlinable public borrowing func power(_ exponent: borrowing Magnitude) -> Self { + self.power(exponent).unchecked() } - /// Returns a `power`. - /// - /// - Returns: `pow(self, exponent) * coefficient` + /// Returns `self` to the power of `exponent`. /// /// ```swift /// I8(0).power(U8(1), coefficient: I8(2)) // I8.exactly( 0) @@ -182,14 +155,9 @@ extension BinaryInteger where Self: ArbitraryInteger & SignedInteger { /// /// ### Exponentiation /// - /// - Note: The default `coefficient` is `1`. - /// /// - Note: The `error` is set if the operation is `lossy`. /// - @inlinable public borrowing func power( - _ exponent: borrowing some UnsignedInteger, - coefficient: borrowing Self = 1 - ) -> Self { - self.power(exponent, coefficient: coefficient).unchecked() + @inlinable public borrowing func power(_ exponent: borrowing some UnsignedInteger) -> Self { + self.power(exponent).unchecked() } } diff --git a/Tests/Benchmarks/BinaryInteger+Exponentiation.swift b/Tests/Benchmarks/BinaryInteger+Exponentiation.swift index 2167c76a..0c0bd850 100644 --- a/Tests/Benchmarks/BinaryInteger+Exponentiation.swift +++ b/Tests/Benchmarks/BinaryInteger+Exponentiation.swift @@ -43,7 +43,7 @@ final class BinaryIntegerBenchmarksOnExponentiation: XCTestCase { } } - /// ###### 2024-09-09 (MacBook Pro, 13-inch, M1, 2020): + /// ###### MacBook Pro, 13-inch, M1, 2020 /// /// 0.44 seconds /// 0.24 seconds after (#84) diff --git a/Tests/Benchmarks/Projects/Exports.swift b/Tests/Benchmarks/Project/Exports.swift similarity index 100% rename from Tests/Benchmarks/Projects/Exports.swift rename to Tests/Benchmarks/Project/Exports.swift diff --git a/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift b/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift index 015cb05d..ba32d369 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift @@ -84,12 +84,11 @@ import TestKit let size = IX(size: T.self) ?? conditional(debug: 32, release: 256) for _ in 0 ..< conditional(debug: 4, release: 16) { - let base = T.entropic(size: size, using: &randomness) - let coefficient = T.entropic(size: size, using: &randomness) - var power = Fallible(coefficient) + let base = T.entropic(size: size, using: &randomness) + var power = Fallible(T.lsb) for exponent: T.Magnitude in 0 ..< 16 { - try #require(base.power(exponent, coefficient: coefficient) == power) + try #require(base.power(exponent) == power) power = power.map{$0.times(base)} } } @@ -108,7 +107,7 @@ import TestKit //=------------------------------------------------------------------------= @Test( - "BinaryInteger/exponentiation/edge-cases: base is 1 → coefficient", + "BinaryInteger/exponentiation/edge-cases: base is 1 → 1", Tag.List.tags(.documentation, .generic, .random), arguments: typesAsBinaryInteger, fuzzers ) func baseIsOneYieldsCoefficient( @@ -120,15 +119,14 @@ import TestKit typealias M = T.Magnitude for _ in 0 ..< 32 { - let a = M.entropic(through: Shift.max(or: 255), using: &randomness) - let b = T.entropic(through: Shift.max(or: 255), using: &randomness) - try #require(T.lsb.power(a, coefficient: b) == Fallible(b)) + let x = M.entropic(through: Shift.max(or: 255), using: &randomness) + try #require(T.lsb.power(x) == Fallible(T.lsb)) } } } @Test( - "BinaryInteger/exponentiation/edge-cases: base is 0 → 0 or coefficient", + "BinaryInteger/exponentiation/edge-cases: base is 0 → 0 or 1", Tag.List.tags(.documentation, .generic, .random), arguments: typesAsBinaryInteger, fuzzers ) func baseIsZeroYieldsZeroOrCoefficient( @@ -142,15 +140,14 @@ import TestKit for _ in 0 ..< 32 { let a = T.zero let b = M.entropic(through: Shift.max(or: 255), using: &randomness) - let c = T.entropic(through: Shift.max(or: 255), using: &randomness) - let d = Fallible(b.isZero ? c : T.zero) - try #require(a.power(b, coefficient: c) == d) + let c = Fallible(b.isZero ? T.lsb : T.zero) + try #require(a.power(b) == c) } } } @Test( - "BinaryInteger/exponentiation/edge-cases: base is NOT(0) → coefficient * (1 or ~0)", + "BinaryInteger/exponentiation/edge-cases: base is NOT(0) → 1 or NOT(0)", Tag.List.tags(.documentation, .generic, .random), arguments: typesAsBinaryInteger, fuzzers ) func baseIsRepeatingOnesYieldsCoefficietExpression( @@ -164,15 +161,14 @@ import TestKit for _ in 0 ..< 32 { let a = T(repeating: Bit.one) let b = M.entropic(through: Shift.max(or: 255), using: &randomness) - let c = T.entropic(through: Shift.max(or: 255), using: &randomness) - let d = c.times(Bool(b.lsb) ? ~0 : 1).veto(!T.isSigned && b >= 2 && !c.isZero) - try #require(a.power(b, coefficient: c) == d) + let c = Fallible(b.lsb.isZero ? T.lsb : a, error: !T.isSigned && b >= 2) + try #require(a.power(b) == c) } } } @Test( - "BinaryInteger/exponentiation/edge-cases: exponent is 0 → coefficient", + "BinaryInteger/exponentiation/edge-cases: exponent is 0 → 1", Tag.List.tags(.documentation, .generic, .random), arguments: typesAsBinaryInteger, fuzzers ) func exponentIsZeroYieldsCoefficientNoError( @@ -182,27 +178,8 @@ import TestKit try whereIs(type) func whereIs(_ type: T.Type) throws where T: BinaryInteger { for _ in 0 ..< 32 { - let a = T.entropic(through: Shift.max(or: 255), using: &randomness) - let b = T.entropic(through: Shift.max(or: 255), using: &randomness) - try #require(a.power(T.Magnitude.zero, coefficient: b) == Fallible(b)) - } - } - } - - @Test( - "BinaryInteger/exponentiation/edge-cases: coefficient is 0 → 0", - Tag.List.tags(.documentation, .generic, .random), - arguments: typesAsBinaryInteger, fuzzers - ) func coefficientIsZeroYieldsZeroNoError( - 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 a = T.entropic(through: Shift.max(or: 255), using: &randomness) - let b = T.Magnitude.entropic(through: Shift.max(or: 255), using: &randomness) - try #require(a.power(b, coefficient: T.zero) == Fallible(T.zero)) + let x = T.entropic(through: Shift.max(or: 255), using: &randomness) + try #require(x.power(T.Magnitude.zero) == Fallible(T.lsb)) } } } @@ -235,9 +212,8 @@ import TestKit for _ in 0 ..< 4 { let base = A.random(using: &randomness) let exponent = B.Magnitude.random(in: min...max, using: &randomness) - let coefficient = A.random(using: &randomness) - let small = A(base).power(exponent, coefficient: A(coefficient)) - let large = B(base).power(exponent, coefficient: B(coefficient)) + let small = A(base).power(exponent) + let large = B(base).power(exponent) try #require(small == large.map(A.exactly)) } } @@ -261,8 +237,6 @@ import TestKit func build(_ x: inout T) where T: BinaryInteger { _ = x.power(0) _ = x.power(0 as UX) - _ = x.power(0, coefficient: 0) - _ = x.power(0 as UX, coefficient: 0) } } @@ -296,10 +270,9 @@ import TestKit for _ in 0 ..< 4 { let a = T.entropic(using: &randomness) let b = M.entropic(using: &randomness) - let c = T.entropic(using: &randomness) - let x = a.power( (b), coefficient:c) - let y = a.power(UXL(b), coefficient:c) - try #require((((((((((x == y)))))))))) + let x = a.power( (b)) + let y = a.power(UXL(b)) + try #require((x == y)) } } } @@ -325,19 +298,12 @@ import TestKit for _ in 0 ..< 4 { let a = T.entropic(size: 032, as: Domain.binary, using: &randomness) let b = U.entropic(size: 004, as: Domain.natural, using: &randomness) - let c = T.entropic(size: 256, as: Domain.binary, using: &randomness) always: do { let x = a.power(b) as Fallible let y = a.power(b) as T try #require(x.optional() == y) } - - always: do { - let x = a.power(b, coefficient: c) as Fallible - let y = a.power(b, coefficient: c) as T - try #require(x.optional() == y) - } } } } @@ -357,19 +323,12 @@ import TestKit for _ in 0 ..< 4 { let a = T.entropic(size: 032, as: Domain.binary, using: &randomness) let b = U.entropic(size: 004, as: Domain.natural, using: &randomness) - let c = T.entropic(size: 256, as: Domain.binary, using: &randomness) always: do { let x = a.power(b) as Fallible let y = a.power(b) as T try #require(x.optional() == y) } - - always: do { - let x = a.power(b, coefficient: c) as Fallible - let y = a.power(b, coefficient: c) as T - try #require(x.optional() == y) - } } } } diff --git a/Tests/UltimathnumTests/BinaryInteger+Factorization.swift b/Tests/UltimathnumTests/BinaryInteger+Factorization.swift index 347c4012..bdcfec96 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Factorization.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Factorization.swift @@ -128,7 +128,7 @@ import TestKit 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) + let power = base.power(exponent).map{$0.times(divisor)} divisor = try #require(power.optional()) } }