From f73dfc422ede029bccaacea881c213ecfb8eeae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Fri, 23 Aug 2024 07:34:37 +0200 Subject: [PATCH] Generic exponent (#72) and free multiplication (#71) in BinaryInteger/power(_:). --- .../BinaryInteger+Exponentiation.swift | 229 ++++++++++++--- Tests/Benchmarks/Exponentiation.swift | 55 ++++ .../BinaryInteger+Exponentiation.swift | 277 +++++++++++++++--- 3 files changed, 482 insertions(+), 79 deletions(-) create mode 100644 Tests/Benchmarks/Exponentiation.swift diff --git a/Sources/CoreKit/BinaryInteger+Exponentiation.swift b/Sources/CoreKit/BinaryInteger+Exponentiation.swift index 897dd0f0..6a606c07 100644 --- a/Sources/CoreKit/BinaryInteger+Exponentiation.swift +++ b/Sources/CoreKit/BinaryInteger+Exponentiation.swift @@ -21,17 +21,25 @@ extension BinaryInteger { /// /// Use `1` systems integer `exponent` type per type of `Self`. /// - @inlinable internal static func raise( - _ instance: borrowing Self, - to exponent: /* borrowing */ Natural + /// Magnitude.size < UX.size: UX or + /// Magnitude.size ≤ IX.max.: Magnitude + /// Magnitude.size > IX.max.: UX + /// + /// - Note: The nonzero `coefficient` simplifies `error` reporting. + /// + @inlinable internal static func resolve( + base: borrowing Self, + power exponent: consuming Natural, + coefficient: /*borrowing*/ Nonzero ) -> Fallible { Swift.assert(!exponent.value.isNegative) Swift.assert(!exponent.value.isInfinite) + Swift.assert(!coefficient.value.isZero) - var power = Fallible(1 as Self) - var multiplier = Fallible(copy instance) - var exponent = (copy exponent).value + var power = Fallible((copy coefficient).value) + var multiplier = Fallible(copy base) + var exponent = exponent.value exponentiation: while true { if Bool(exponent.lsb) { @@ -52,37 +60,116 @@ extension BinaryInteger { // MARK: Transformations //=------------------------------------------------------------------------= - /// Returns the `power` and `error` of `self` raised to `exponent`. + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` /// /// ```swift - /// U8(1).power(2) // value: 001, error: false - /// U8(2).power(3) // value: 008, error: false - /// U8(3).power(5) // value: 243, error: false - /// U8(5).power(7) // value: 045, error: true + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true /// ``` /// - @inlinable public borrowing func power(_ exponent: /* borrowing */ Magnitude) -> Fallible { - if !Self.isArbitrary { - return Self.raise(self, to: Natural(unchecked: exponent)) + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public /*borrowing*/ func power( + _ exponent: some UnsignedInteger, + coefficient: borrowing Nonzero + ) -> Fallible { + + if !Magnitude.isArbitrary { + var (magic, error) = Magnitude.exactly(exponent).components() + if (error) { + switch Bool(self.lsb) { + case true: error = self.magnitude() > 1 //........ cycle + case false: return Self.zero.veto(!self.isZero) // zeros + } + } + return Self.resolve(base: self, power: Natural(unchecked: magic), coefficient: coefficient).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: self, power: Natural(unchecked: magic), coefficient: coefficient) + } + } + + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` + /// + /// ```swift + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true + /// ``` + /// + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: some UnsignedInteger, + coefficient: borrowing Self = 1 + ) -> Fallible { + + if let coefficient = Nonzero(exactly: copy coefficient) { + return self.power(exponent, coefficient: coefficient) } 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.raise(self, to: Natural(unchecked: magic)) + return Fallible(copy coefficient) } } - /// Returns the `power` and `error` of `self` raised to `exponent`. + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` /// /// ```swift - /// U8(1).power(2) // value: 001, error: false - /// U8(2).power(3) // value: 008, error: false - /// U8(3).power(5) // value: 243, error: false - /// U8(5).power(7) // value: 045, error: true + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true /// ``` /// - @inlinable public borrowing func power(_ exponent: borrowing Fallible) -> Fallible { - self.power(exponent.value).veto(exponent.error) + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: borrowing Fallible, + coefficient: borrowing Nonzero + ) -> Fallible { + self.power(exponent.value, coefficient: coefficient).veto(exponent.error) + } + + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` + /// + /// ```swift + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true + /// ``` + /// + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: borrowing Fallible, + coefficient: borrowing Self = 1 + ) -> Fallible { + self.power(exponent.value, coefficient: coefficient).veto(exponent.error) } } @@ -96,29 +183,95 @@ extension Fallible where Value: BinaryInteger { // MARK: Transformations //=------------------------------------------------------------------------= - /// Returns the `power` and `error` of `self` raised to `exponent`. + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` /// /// ```swift - /// U8(1).power(2) // value: 001, error: false - /// U8(2).power(3) // value: 008, error: false - /// U8(3).power(5) // value: 243, error: false - /// U8(5).power(7) // value: 045, error: true + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true /// ``` /// - @inlinable public borrowing func power(_ exponent: borrowing Value.Magnitude) -> Fallible { - self.value.power(exponent).veto(self.error) + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: borrowing some UnsignedInteger, + coefficient: borrowing Nonzero + ) -> Fallible { + self.value.power(exponent, coefficient: coefficient).veto(self.error) } - /// Returns the `power` and `error` of `self` raised to `exponent`. + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` /// /// ```swift - /// U8(1).power(2) // value: 001, error: false - /// U8(2).power(3) // value: 008, error: false - /// U8(3).power(5) // value: 243, error: false - /// U8(5).power(7) // value: 045, error: true + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true /// ``` /// - @inlinable public borrowing func power(_ exponent: borrowing Fallible) -> Fallible { - self.power(exponent.value).veto(exponent.error) + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: borrowing some UnsignedInteger, + coefficient: borrowing Value = 1 + ) -> Fallible { + self.value.power(exponent, coefficient: coefficient).veto(self.error) + } + + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` + /// + /// ```swift + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true + /// ``` + /// + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: borrowing Fallible, + coefficient: borrowing Nonzero + ) -> Fallible { + self.power(exponent.value, coefficient: coefficient).veto(exponent.error) + } + + /// Returns a `power` and an `error` indiactor. + /// + /// - Returns: `pow(self, exponent) * coefficient` + /// + /// ```swift + /// U8(0).power(0, coefficient: 0) // value: 0, error: false + /// U8(0).power(0, coefficient: 1) // value: 1, error: false + /// U8(0).power(1, coefficient: 2) // value: 0, error: false + /// U8(1).power(2, coefficient: 3) // value: 3, error: false + /// U8(2).power(3, coefficient: 5) // value: 40, error: false + /// U8(3).power(5, coefficient: 7) // value: 165, error: true + /// U8(5).power(7, coefficient: 11) // value: 239, error: true + /// ``` + /// + /// - Note: The default `coefficient` is `1`. + /// + @inlinable public borrowing func power( + _ exponent: borrowing Fallible, + coefficient: borrowing Value = 1 + ) -> Fallible { + self.power(exponent.value, coefficient: coefficient).veto(exponent.error) } } diff --git a/Tests/Benchmarks/Exponentiation.swift b/Tests/Benchmarks/Exponentiation.swift new file mode 100644 index 00000000..4616219a --- /dev/null +++ b/Tests/Benchmarks/Exponentiation.swift @@ -0,0 +1,55 @@ +//=----------------------------------------------------------------------------= +// 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 InfiniIntKit +import TestKit + +//*============================================================================* +// MARK: * Exponentiation +//*============================================================================* + +/// - Important: Please disable code coverage because it is always on by default. +final class ExponentiationBenchmarks: XCTestCase { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + func testExponentiationAsUX() throws { + typealias T = UX + + for base: UX in 0..<128 { + for exponent: UX in 0..<1000 { + blackHole(T(load: base).power(T(load: exponent))) + } + } + } + + func testExponentiationAsU256() throws { + typealias T = U256 + + for base: UX in 0..<128 { + for exponent: UX in 0..<1000 { + blackHole(T(load: base).power(T(load: exponent))) + } + } + } + + func testExponentiationAsUXL() throws { + typealias T = UXL + + for base: UX in 0..<128 { + for exponent: UX in 0..<1000 { + blackHole(T(load: base).power(T(load: exponent))) + } + } + } +} diff --git a/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift b/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift index a75835b7..121305da 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift @@ -23,23 +23,6 @@ final class BinaryIntegerTestsOnExponentiation: XCTestCase { // MARK: Tests //=------------------------------------------------------------------------= - func testRaiseTrivialCases() { - func whereIs(_ type: T.Type) where T: BinaryInteger { - let small = ( 0 as T.Magnitude) ... 11 - let large = (~4 as T.Magnitude) ... T.Magnitude.max - - for exponent: T.Magnitude in [small, large].joined() { - Test().same(( 0 as T).power(exponent), Fallible(exponent.isZero ? 00001 : 0)) - Test().same(( 1 as T).power(exponent), Fallible(1)) - Test().same((~0 as T).power(exponent), Fallible(Bool(exponent.lsb) ? ~0 : 1, error: !T.isSigned && exponent >= 2)) - } - } - - for type in binaryIntegers { - whereIs(type) - } - } - /// https://www.wolframalpha.com/input?i2d=true&i=Power%5B3%2C2239%5D func testRaiseThreeToPrime333() { let expectation = UXL(""" @@ -62,15 +45,37 @@ final class BinaryIntegerTestsOnExponentiation: XCTestCase { 6036250169540192297202679456092601217013126286587177412655204267 """)! - func whereIs(_ type: T.Type) where T: BinaryInteger { - if let exponent = T.Magnitude.exactly(2239).optional() { - Test().same(T(3).power(exponent), T.exactly(expectation)) + func whereIs(_ type: A.Type, _ exponent: B.Type) where A: BinaryInteger, B: UnsignedInteger { + if let exponent = B.exactly(2239).optional() { + Test().same(A(3).power(exponent), A.exactly(expectation)) } } + #if DEBUG + whereIs( IX .self, UX .self) + whereIs( UX .self, UX .self) + whereIs(DoubleInt.self, DoubleInt.self) + whereIs(DoubleInt.self, DoubleInt.self) + whereIs(InfiniInt.self, InfiniInt.self) + whereIs(InfiniInt.self, InfiniInt.self) + #else for type in binaryIntegers { - whereIs(type) + for exponent in binaryIntegersWhereIsUnsigned { + whereIs(type, exponent) + } } + #endif + } + + func testCoefficientIsOneByDefault() { + Test().same(I8(2).power(UX(3)), Fallible(8)) + Test().same(U8(2).power(UX(3)), Fallible(8)) + Test().same(Fallible(I8(2)).power(UX(3)), Fallible(8)) + Test().same(Fallible(U8(2)).power(UX(3)), Fallible(8)) + Test().same(I8(2).power(Fallible(UX(3))), Fallible(8)) + Test().same(U8(2).power(Fallible(UX(3))), Fallible(8)) + Test().same(Fallible(I8(2)).power(Fallible(UX(3))), Fallible(8)) + Test().same(Fallible(U8(2)).power(Fallible(UX(3))), Fallible(8)) } //=------------------------------------------------------------------------= @@ -87,10 +92,11 @@ final class BinaryIntegerTestsOnExponentiation: XCTestCase { for _ in 0 ..< rounds { let base = random() - var power = Fallible(1 as T) - + let coefficient = random() + var power = Fallible(coefficient) + for exponent: T.Magnitude in 0 ..< 8 { - Test().same(base.power(exponent), power) + Test().same(base.power(exponent, coefficient: coefficient), power) power = power.times(base) } } @@ -98,7 +104,7 @@ final class BinaryIntegerTestsOnExponentiation: XCTestCase { for type in binaryIntegers { #if DEBUG - whereIs(type, size: IX(size: type) ?? 0032, rounds: 01, randomness: fuzzer) + whereIs(type, size: IX(size: type) ?? 0032, rounds: 04, randomness: fuzzer) #else whereIs(type, size: IX(size: type) ?? 0256, rounds: 16, randomness: fuzzer) #endif @@ -106,25 +112,170 @@ final class BinaryIntegerTestsOnExponentiation: XCTestCase { } func testRaiseByFuzzing() { - func whereIs(_ type: T.Type, rounds: IX, randomness: consuming FuzzerInt) where T: BinaryInteger { - let max = T.isArbitrary ? 255 : T.Magnitude.max + func whereIs(_ type: T.Type, size: IX, rounds: IX, randomness: consuming FuzzerInt) where T: BinaryInteger { + let limit = T.isArbitrary ? 255 : T.Magnitude.max + func random() -> T { + let index = IX.random(in: 00000 ..< size, using: &randomness)! + let pattern = T.Signitude.random(through: Shift(Count(index)), using: &randomness) + return T(raw: pattern) // do not forget about infinite values! + } + for _ in 0 ..< rounds { - let x = T.Magnitude.random(in: 0 ... max, using: &randomness) - let a = T(3).times(T(5)).power(x) - let b = T(3).power(x).times(T( 5).power(x)) - Test().same(a, b) + let coefficient: T = random() + let exponent = T.Magnitude.random(in: 0...limit, using: &randomness) + let result = T(3).times(5).power(exponent, coefficient: coefficient) + + if coefficient.isZero { + Test().same(result, Fallible(T.zero)) + } else { + Test().same(result, T(3).power(exponent).times(T(5).power(exponent)).times(coefficient)) + } } } for type in binaryIntegers { #if DEBUG - whereIs(type, rounds: 02, randomness: fuzzer) + whereIs(type, size: IX(size: type) ?? 255, rounds: 04, randomness: fuzzer) #else - whereIs(type, rounds: 16, randomness: fuzzer) + whereIs(type, size: IX(size: type) ?? 255, rounds: 16, randomness: fuzzer) #endif } } + + /// - Note: Exponents beyond `T.Magnitude.max` repeat or return zero. + func testExponentsThatDontFitInMagnitudeAsSystemsInteger() { + func whereIs(_ type: T.Type) where T: SystemsInteger { + let exponent = UXL.lsb << IX(size: type) + for coefficient: T in (I8(-2)...I8(2)).lazy.map(T.init(load:)) { + Test().same((~8 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same((~7 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same((~6 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same((~5 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same((~4 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same((~3 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same((~2 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same((~1 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same((~0 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero && !T.isSigned)) + Test().same(( 0 as T).power(exponent, coefficient: coefficient), Fallible(00000000000)) + Test().same(( 1 as T).power(exponent, coefficient: coefficient), Fallible(coefficient)) + Test().same(( 2 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same(( 3 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same(( 4 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same(( 5 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same(( 6 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + Test().same(( 7 as T).power(exponent, coefficient: coefficient), Fallible(coefficient, error: !coefficient.isZero)) + Test().same(( 8 as T).power(exponent, coefficient: coefficient), Fallible(00000000000, error: !coefficient.isZero)) + } + } + + for type in systemsIntegers { + whereIs(type) + } + } + + func testExponentsThatDontFitInMagnitudeAsSystemsIntegerByFuzzing() { + func whereIs(small: A.Type, large: B.Type, rounds: IX, randomness: consuming FuzzerInt) where A: SystemsInteger, B: SystemsInteger { + guard A.size <= B.size, A.isSigned == B.isSigned else { return } + + for _ in 0 ..< rounds { + let base = A.random(using: &randomness) + let exponent = B.Magnitude.random(in: B.Magnitude(A.Magnitude.max)...B.Magnitude.max) + let coefficient = A.random(using: &randomness) + let small = A(base).power(exponent, coefficient: A(coefficient)) + let large = B(base).power(exponent, coefficient: B(coefficient)) + Test().same(small, A.exactly(large.value).veto(large.error)) + } + } + + for small in systemsIntegers { + for large in systemsIntegers { + #if DEBUG + whereIs(small: small, large: large, rounds: 4, randomness: fuzzer) + #else + whereIs(small: small, large: large, rounds: 8, randomness: fuzzer) + #endif + } + } + } +} + +//=----------------------------------------------------------------------------= +// MARK: + Edge Cases +//=----------------------------------------------------------------------------= + +extension BinaryIntegerTestsOnExponentiation { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + func testBasesNearZero() { + func whereIs(_ type: T.Type) where T: BinaryInteger { + for exponent: I8 in -2...2 { + let exponent = T.Magnitude(load: exponent) + for coefficient: I8 in -2...2 { + let coefficient = T(load: coefficient) + + Test().same( + T(1 as Bit).power(exponent, coefficient: coefficient), + Fallible(coefficient) + ) + + Test().same( + T(repeating: 0 as Bit).power(exponent, coefficient: coefficient), + Fallible(exponent.isZero ? coefficient : T.zero) + ) + + Test().same( + T(repeating: 1 as Bit).power(exponent, coefficient: coefficient), + coefficient.times(Bool(exponent.lsb) ? ~0 : 1).veto( + !T.isSigned && exponent >= 2 && !coefficient.isZero + ) + ) + } + } + } + + #if DEBUG + whereIs(UX.self) + whereIs(DoubleInt.self) + whereIs(InfiniInt.self) + #else + for type in binaryIntegers { + whereIs(type) + } + #endif + } + + func testZeroCoefficientsReturnZeroNoError() { + func whereIs(_ type: A.Type, _ exponent: B.Type) where A: BinaryInteger, B: UnsignedInteger { + Test().same(Esque.min.power(B.max, coefficient: A.zero), Fallible(A.zero)) + Test().same(Esque.max.power(B.max, coefficient: A.zero), Fallible(A.zero)) + } + + for type in binaryIntegers { + for exponent in binaryIntegersWhereIsUnsigned { + whereIs(type, exponent) + } + } + } + + func testZeroExponentsReturnCoefficientNoError() { + func whereIs(_ type: A.Type, _ exponent: B.Type) where A: BinaryInteger, B: UnsignedInteger { + for coefficient: I8 in -2...2 { + let coefficient = A(load: coefficient) + Test().same(Esque.min.power(B.min, coefficient: coefficient), Fallible(coefficient)) + Test().same(Esque.max.power(B.min, coefficient: coefficient), Fallible(coefficient)) + } + } + + for type in binaryIntegers { + for exponent in binaryIntegersWhereIsUnsigned { + whereIs(type, exponent) + } + } + } } //=----------------------------------------------------------------------------= @@ -163,12 +314,45 @@ extension BinaryIntegerTestsOnExponentiation { success &+= IX(Bit(base.veto(true ).power(exponent.veto(true )) == expectation.veto())) } - Test().same(success, rounds &* 9) + for _ in 0 ..< rounds { + let base: T = random() + let exponent = T.Magnitude.random(in: 0...max, using: &randomness) + let coefficient: T = random() + let expectation: Fallible = base.power(exponent, coefficient: coefficient) + success &+= IX(Bit(base .power(exponent, coefficient: coefficient) == expectation)) + success &+= IX(Bit(base .power(exponent.veto(false), coefficient: coefficient) == expectation)) + success &+= IX(Bit(base .power(exponent.veto(true ), coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(false).power(exponent, coefficient: coefficient) == expectation)) + success &+= IX(Bit(base.veto(false).power(exponent.veto(false), coefficient: coefficient) == expectation)) + success &+= IX(Bit(base.veto(false).power(exponent.veto(true ), coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(true ).power(exponent, coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(true ).power(exponent.veto(false), coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(true ).power(exponent.veto(true ), coefficient: coefficient) == expectation.veto())) + + if let coefficient = Nonzero(exactly: coefficient.isZero ? T.lsb : coefficient) { + let expectation: Fallible = base.power(exponent, coefficient: coefficient) + success &+= IX(Bit(base .power(exponent, coefficient: coefficient) == expectation)) + success &+= IX(Bit(base .power(exponent.veto(false), coefficient: coefficient) == expectation)) + success &+= IX(Bit(base .power(exponent.veto(true ), coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(false).power(exponent, coefficient: coefficient) == expectation)) + success &+= IX(Bit(base.veto(false).power(exponent.veto(false), coefficient: coefficient) == expectation)) + success &+= IX(Bit(base.veto(false).power(exponent.veto(true ), coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(true ).power(exponent, coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(true ).power(exponent.veto(false), coefficient: coefficient) == expectation.veto())) + success &+= IX(Bit(base.veto(true ).power(exponent.veto(true ), coefficient: coefficient) == expectation.veto())) + } + } + + Test().same(success, rounds &* 27) } - for type in binaryIntegers { - whereIs(type, size: IX(size: type) ?? 32, rounds: 4, randomness: fuzzer) - } + // note that existentials are too slow for this + whereIs( I8 .self, size: 8, rounds: 8, randomness: fuzzer) + whereIs( U8 .self, size: 8, rounds: 8, randomness: fuzzer) + whereIs(DoubleInt.self, size: 16, rounds: 8, randomness: fuzzer) + whereIs(DoubleInt.self, size: 16, rounds: 8, randomness: fuzzer) + whereIs(InfiniInt.self, size: 16, rounds: 8, randomness: fuzzer) + whereIs(InfiniInt.self, size: 16, rounds: 8, randomness: fuzzer) } } @@ -183,9 +367,20 @@ extension BinaryIntegerTestsOnExponentiation { //=------------------------------------------------------------------------= func testBinaryIntegerDocumentation() { - Test().same(U8(1).power(2), Fallible(001)) - Test().same(U8(2).power(3), Fallible(008)) - Test().same(U8(3).power(5), Fallible(243)) - Test().same(U8(5).power(7), Fallible(045, error: true)) + func whereIs(_ type: A.Type, _ exponent: B.Type) where A: BinaryInteger, B: UnsignedInteger { + Test().same(A(0).power(B(0), coefficient: A( 0)), A.exactly( 0)) + Test().same(A(0).power(B(0), coefficient: A( 1)), A.exactly( 1)) + Test().same(A(0).power(B(1), coefficient: A( 2)), A.exactly( 0)) + Test().same(A(1).power(B(2), coefficient: A( 3)), A.exactly( 3)) + Test().same(A(2).power(B(3), coefficient: A( 5)), A.exactly( 40)) + Test().same(A(3).power(B(5), coefficient: A( 7)), A.exactly( 1701)) + Test().same(A(5).power(B(7), coefficient: A(11)), A.exactly(859375)) + } + + for type in binaryIntegers { + for exponent in binaryIntegersWhereIsUnsigned { + whereIs(type, exponent) + } + } } }