Exponents in Swift

Overhaul of Exponents in Swift

I use Foundation's pow(_:_:) function often when performing calculations. This function for me has one major draw back, it does not have an overload for Int. Better yet, the introduction of an exponent operator would help simplify this arithmetic operation's use even more so.


I often find myself writing:

var x: Int = 5
var y: Int = 2

var n = Int(pow(Double(x), Double(y)))

Since the majority of the time I find my self using pow(_:_:) with just Int, this gets quite annoying to write.

It feels quite unnecessary to perform all of these type conversions and would be made much easier with the introduction of overloads to the pow(_:_:) function that support Int.

Possible overloads:

func pow(_ x: Int, _ y: Int) -> Int { ... }
func pow(_ x: Int, _ y: Double) -> Double { ... }
func pow(_ x: Double, _ y: Int) -> Double { ... }

Exponent Operator

Exponents in Swift are frankly annoying to work with as is no matter how you slice. The addition of an exponent operator like ** as a protocol extension on Numeric would make its use much easier all around the board.


What are your thoughts on the addition of these overloads?

Also, what are your thoughts on the addition of an exponent operator ** as a protocol extension for on Numeric?

4 Likes

For the record, pow isn't actually part of Foundation; it's part of the system's libc. That doesn't fundamentally change anything here; either way you're proposing to add new, Swift-only overloads to something that exists in C. I think we'd probably rather come up with a Swifty spelling for exponentiation and add it consistently to the integer and floating-point protocols.

I suspect the reason there's no Int ** Int in C is because you very quickly end up overflowing Int with all but the smallest exponents (or trivial bases 0/1/-1). Even setting that aside, Double ** Int or Int ** Double would probably return a Double, no?

5 Likes

Sorry, that was a typo. You are correct, they should return a Double. I've updated my post.

It's not advisable to use floating-point pow for integer exponentiation because it's not guaranteed to be accurate. For the typical small exponents one would use it's also far less efficient than exponentiation by squaring. I think it'd be nice to have standard integer-to-integer and float-to-integer power operations since these can be done faster and more precisely than pow. Integer-to-float power seems less useful though as a specialization; it wouldn't really give you any better accuracy or efficiency. @scanon probably has opinions about this.

8 Likes

As a practical matter, on Apple's platforms, using pow with integer arguments will always give you the correct integer result after conversion, if it is representable. This is very much not the case with many other platforms, most relevantly linux with older versions of glibc.

Like @jrose says, almost all argument pairs overflow in any fixed-with integer type, so the operation has limited usefulness. What's much more commonly useful is modular exponentiation, the operation that computes a^b mod c, since that never overflows (and there are lots of interesting tricks to make it efficient). Out of curiosity, what are you using it for that you're not running into this issue?

Double ** Int is more interesting since it doesn't suffer from the overflow problem, but it really can't be made much more efficient than Double ** Double in the general case, so the wins there are limited. In as much as it could be more efficient, it would actually be less accurate on platforms with a high-quality libm implementation.

7 Likes

It doesn't seem unreasonable to me to want to do small power operations and write x**4 instead of x*x*x*x (or, if you're trying to be optimal, {[x2 = x*x] in x2*x2 }()). That's probably the biggest use case for integer pow, as a small power rather than an exponentiation operator.

8 Likes

I used when defining an exponentiation operator on a custom Fraction type I made (SwiftFractions) that uses Int as its numerator and denominator.

Don't forget, you can define your own operators. Here's a definition of the exponent operator that accepts any two numbers of the same type:

precedencegroup Exponentiative {
  associativity: left
  higherThan: MultiplicationPrecedence
}

infix operator ** : Exponentiative

public func ** <N: BinaryInteger>(base: N, power: N) -> N {
    return N.self( pow(Double(base), Double(power)) )
}

public func ** <N: BinaryFloatingPoint>(base: N, power: N) -> N {
    return N.self ( pow(Double(base), Double(power)) )
}

Usage:

let x: Int = 5
let y: Int = x ** 2
// y = 25

And why stop there? Here's a exponent compound assignment operator:

precedencegroup ExponentiativeAssignment {
  associativity: right
  higherThan: MultiplicationPrecedence
}

infix operator **= : ExponentiativeAssignment

public func **= <N: BinaryInteger>(lhs: inout N, rhs: N) {
    lhs = lhs ** rhs
}

public func **= <N: BinaryFloatingPoint>(lhs: inout N, rhs: N) {
    lhs = lhs ** rhs
}

Usage:

var x = 5
x **= 2
// x = 25
2 Likes

I tried using this in an Xcode 11.6 playground. This is what I see:
!Screen Shot 2020-07-18 at 1.19.31 PM|690x349
I have Foundation imported and UIKit

You need to post the specific error that Xcode emitted. The screenshot you posted doesn't display that. My guess is that you only copied the code for the compound exponent assignment operator **= and not the code for the exponent operator **.

Thanks.

I have since changed the code for the exponent operators. You should use this version:

precedencegroup ExponentiativePrecedence {
  associativity: right
  higherThan: MultiplicationPrecedence
}

infix operator ** : ExponentiativePrecedence

/// Exponent operator.
public func ** <N: BinaryInteger>(base: N, power: N) -> N {
    return N.self(pow(Double(base), Double(power)))
}

/// Exponent operator.
public func ** <N: BinaryFloatingPoint>(base: N, power: N) -> N {
    return N.self(pow(Double(base), Double(power)))
}


infix operator **= : AssignmentPrecedence

public func **= <N: BinaryInteger>(lhs: inout N, rhs: N) {
    lhs = lhs ** rhs
}

public func **= <N: BinaryFloatingPoint>(lhs: inout N, rhs: N) {
    lhs = lhs ** rhs
}
5 Likes

Awesome