What's the most general type operator `/` can be used on?

I am not able to search for the doc on / or see the source in Xcode. I want to make a generic func and one thing it needs is divide some numeric by 2. But I can't even make two Numeric divide:

func blah000<T: Numeric>(_ pair: (T, T)) -> T {
    pair.1 / pair.0 // compile error: Binary operator '/' cannot be applied to two 'T' operands
}

Maybe Numeric is not the right type. So I want to know what's the top most general type / can be applied?

I don't think there's a single top-level protocol that defines /. The same / operator is defined separate by BinaryInteger and FloatingPoint, but the two have different semantics (integer division vs floating-point division).

IIRC the Swift numerics package had a protocol that unified these, but I don't remember the details.

It does not.

Numerics has a protocol AlgebraicField which includes division, but integer types do not conform because the integers are not a field.

3 Likes

Is there better way? So much boilerplates:

protocol AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self
    static func half(_ lhs: Self) -> Self
}

// these do not work:
//extension BinaryInteger: AddableAndDivisible { }    // Extension of protocol 'BinaryInteger' cannot have an inheritance clause
//extension FloatingPoint: AddableAndDivisible { }    // Extension of protocol 'FloatingPoint' cannot have an inheritance clause

extension Double: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension Float: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension Int32: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension Int: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension Int64: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension Int16: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension UInt8: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension UInt32: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension UInt: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension UInt64: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}
extension UInt16: AddableAndDivisible {
    static func add(_ lhs: Self, _ rhs: Self) -> Self { lhs + rhs }
    static func half(_ lhs: Self) -> Self { lhs / 2 }
}

Would rather half is divide, but can't figure out how to divide by literal 2

You almost certainly do not want to do this.

What are you actually trying to achieve?

Given two numbers, sum then divided by 2:

func foo<T: __AnyAndAllNumericType__>(_ a: T, _ b: T) -> T {
    (a + b) / 2
}

It sounds like you're trying to do something like the following:

func average<T>(_ a: T, _ b: T) -> T where T: FloatingPoint {
  (a + b) / 2
}

func average<T>(_ a: T, _ b: T) -> T where T: BinaryInteger {
   (a + b) / 2 // but see notes below
}

The second one of these is a bad (or at least not fully-considered) implementation of average, for a few reasons:

  • it rounds the result toward zero, which will introduce a bias into some computations.
  • it does not handle intermediate overflow correctly, even though the average of two integers is always representable in the same type. E.g. average(64 as Int8, 64) crashes instead of producing 64.

Note that these caveats do not apply to the first implementation, because integer and floating-point arithmetic are fundamentally different. Because of this, you actually want to have separate implementations so you can address these issues for integers.

5 Likes

Do you have in mind an algorithm where this is useful for both integers with integer division, and floats with floating-point division?

Those are very different operations, and it sounds like you are trying to conflate them simply because they happen to be spelled the same.

Imagine for a moment that Swift did not use the “/” symbol for either operation, and instead spelled them in words:

divideIntegers(a, b)

divideFloats(a, b)

If those were the spellings, would you still be trying to merge them?

3 Likes

I'm trying to avoid duplicating average because it's doing some somewhat complicated long winded algorithm...

What is this algorithm which you seem to think works correctly for both integers averaged with integer division and floats averaged with floating-point division?

To repeat my original question, what are you actually trying to achieve?

It's some gaming probability calculation.

But you are right that divide do not work generically between integer and floating point, so it's not a good idea to mix them.

But I am trying to figure out for my own case how to write generic code to mix the two kind divide without duplicating. If there is no way, then I will just repeat.

Edit: So divide Integer vs Float can be solved by making my algorithm to work with Double only. But I still want to be able to apply this algorithm to any numeric type, except now I cast all value to Double first. So now I need this:

func convert<T: Numeric>(_ arg: T) -> Double {
    arg as Double       // how to do this?
    // or somehow to this:
    NSNumber(value: arg).doubleValue
}

Again, this would just be a separate override that constrains to BinaryInteger, as there's a Double initializer which takes a BinaryInteger.

2 Likes