Skip to content

Commit

Permalink
Simpler power(_:) function! (#155).
Browse files Browse the repository at this point in the history
* Simpler `power(_:)` function! (#153).

This patch simplifies exponentiation by replacing `power(_:coefficient:)` with `power(_:)`.

* Cleanup.

* Cleanup.

* Cleanup.
  • Loading branch information
oscbyspro authored Dec 19, 2024
1 parent dc8752d commit 0e8b38e
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 125 deletions.
92 changes: 30 additions & 62 deletions Sources/CoreKit/BinaryInteger+Exponentiation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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<some UnsignedInteger>,
coefficient: borrowing Nonzero<Self>
@inlinable internal static func perform(
_ base: consuming Self,
power exponent: consuming Natural<some UnsignedInteger>
) -> Fallible<Self> {

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<Self> {

guard let coefficient = Nonzero(exactly: copy coefficient) else {
return Fallible(Self.zero)
}

if !Self.isArbitrary {
var (magic, error) = Magnitude.exactly(exponent).components()
if (error) {
Expand All @@ -74,21 +70,19 @@ 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))
}
}

//=------------------------------------------------------------------------=
// 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)
Expand All @@ -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> {
Self.resolve(self, power: exponent, coefficient: coefficient)
@inlinable public borrowing func power(_ exponent: borrowing Magnitude) -> Fallible<Self> {
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)
Expand All @@ -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> {
Self.resolve(self, power: exponent, coefficient: coefficient)
@inlinable public borrowing func power(_ exponent: borrowing some UnsignedInteger) -> Fallible<Self> {
Self.resolve(self, power: exponent)
}
}

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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()
}
}
2 changes: 1 addition & 1 deletion Tests/Benchmarks/BinaryInteger+Exponentiation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
File renamed without changes.
81 changes: 20 additions & 61 deletions Tests/UltimathnumTests/BinaryInteger+Exponentiation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
}
}
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -182,27 +178,8 @@ import TestKit
try whereIs(type)
func whereIs<T>(_ 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<T>(_ 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))
}
}
}
Expand Down Expand Up @@ -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))
}
}
Expand All @@ -261,8 +237,6 @@ import TestKit
func build<T>(_ 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)
}
}

Expand Down Expand Up @@ -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))
}
}
}
Expand All @@ -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<T>
let y = a.power(b) as T
try #require(x.optional() == y)
}

always: do {
let x = a.power(b, coefficient: c) as Fallible<T>
let y = a.power(b, coefficient: c) as T
try #require(x.optional() == y)
}
}
}
}
Expand All @@ -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<T>
let y = a.power(b) as T
try #require(x.optional() == y)
}

always: do {
let x = a.power(b, coefficient: c) as Fallible<T>
let y = a.power(b, coefficient: c) as T
try #require(x.optional() == y)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/UltimathnumTests/BinaryInteger+Factorization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
Expand Down

0 comments on commit 0e8b38e

Please sign in to comment.