3

Operator Overloading

Swift 4.1, Xcode 9.3

I am trying to make a quadratic equation function in Swift.

While working on this, I found that I needed to overload some operators so that I could work with tuples alongside other numbers (Double in this case), this is because I needed to use my custom ± operator. Despite the fact that I was only working with value of type Double in my quadratic function, I decided that I wanted to use generics to make my overloaded operators more flexible for future use.

But–for a reason I don't understand–I am receiving errors regarding the declaration of the overloaded / operator.


Custom ± Operator - Working

infix operator ± : AdditionPrecedence

public func ± <T: Numeric>(left: T, right: T) -> (T, T) {
    return (left + right, left - right)
}

Quadratic Function - Working

func quadratic(a: Double, b: Double, c: Double) -> (Double, Double) {
    return (-b ± sqrt((b * b) - (4 * a * c))) / (2 * a)
}

Overloaded Operators - Partially Working¹

//Binary operator '/' cannot be applied to two 'T' operands
func / <T: Numeric>(lhs: (T, T), rhs: T) -> (T, T) { 
    return (lhs.0 / rhs, lhs.1 / rhs)
}
func * <T: Numeric>(lhs: (T, T), rhs: T) -> (T, T) {
    return (lhs.0 * rhs, lhs.1 * rhs)
}
func - <T: Numeric>(lhs: (T, T), rhs: T) -> (T, T) {
    return (lhs.0 - rhs, lhs.1 - rhs)
}
func + <T: Numeric>(lhs: (T, T), rhs: T) -> (T, T) {
    return (lhs.0 + rhs, lhs.1 + rhs)
}

//Binary operator '/' cannot be applied to two 'T' operands
func / <T: Numeric>(lhs: T, rhs: (T, T)) -> (T, T) {
    return (lhs / rhs.0, lhs / rhs.1)
}
func * <T: Numeric>(lhs: T, rhs: (T, T)) -> (T, T) {
    return (lhs * rhs.0, lhs * rhs.1)
}
func - <T: Numeric>(lhs: T, rhs: (T, T)) -> (T, T) {
    return (lhs - rhs.0, lhs - rhs.1)
}
func + <T: Numeric>(lhs: T, rhs: (T, T)) -> (T, T) {
    return (lhs + rhs.0, lhs + rhs.1)
}

1. I only receive these errors with the / operator, not with any of the other overloaded operators (+, -, or *).


Overloaded Operators With Errors (/s) - Not Working

//Binary operator '/' cannot be applied to two 'T' operands
func / <T: Numeric>(lhs: (T, T), rhs: T) -> (T, T) { 
    return (lhs.0 / rhs, lhs.1 / rhs)
}

//Binary operator '/' cannot be applied to two 'T' operands
func / <T: Numeric>(lhs: T, rhs: (T, T)) -> (T, T) {
    return (lhs / rhs.0, lhs / rhs.1)
}

Hypothesis: I think that the fact that I am using the operator / within the declaration of the overloaded / operator itself–despite the fact that it is being used in a different context–is causing the error.


Final Question:

How do I resolve the errors while still maintaining the flexibility of my overloaded operators?


Bonus Question 1: If I can (I think that I would probably have to return a String to accomplish this), I would also like to make a separate quadratic function that can return an exact solution?

Bonus Question 2: Also if anyone knows how to make a separate function that can solve a cubic equation rather than quadratic, that would be appreciated too.


Community
  • 1
  • 1
Noah Wilder
  • 1,656
  • 20
  • 38
  • I don't think a pair (tuple of size 2) is an appropriate choice for the result. I think it would be better to use an enum that can properly model the 3 cases: two solutions, one solution, zero solutions – Alexander May 11 '18 at 16:16
  • @Alexander, do you think it would be a good idea to make a `struct` containing variables: `var a1 : Double`, `var a2 : Double`, and return this `struct` instead. Also, what are the benefits to this? – Noah Wilder May 11 '18 at 16:20
  • No, because that struct can't model having 1 value, or 0 values. You need an enum. – Alexander May 11 '18 at 16:24
  • The `Numeric` protocol does not define a division. If you use `FloatingPoint` instead then your division operators compile. Compare https://stackoverflow.com/q/49899377/1187415 – Martin R May 11 '18 at 16:29

2 Answers2

3

Numeric only declares +, - and * operators. There is no / in Numeric. You need either BinaryInteger or FloatingPoint to get division.

For a quadratic equation solver, I would say FloatingPoint is more appropriate:

func / <T: FloatingPoint>(lhs: T, rhs: (T, T)) -> (T, T) {
    return (lhs / rhs.0, lhs / rhs.1)
}

func / <T: FloatingPoint>(lhs: (T, T), rhs: T) -> (T, T) {
    return (lhs.0 / rhs, lhs.1 / rhs)
}

As Alexander has said, it would be better if you created an enum representing the solutions:

enum QuadraticSolution {
    case none
    case one(value: Double)
    case two(value1: Double, value2: Double)
}

func quadratic(a: Double, b: Double, c: Double) -> QuadraticSolution {
    let discriminant = (b * b) - (4 * a * c)
    switch discriminant {
    case 0:
        return .one(value: -b / (2 * a))
    case let x where x < 0:
        return .none
    default:
        let twoSolutions = (-b ± sqrt(discriminant)) / (2 * a)
        return .two(value1: twoSolutions.0, value2: twoSolutions.1)
    }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • What is the difference between `BinaryInteger` and `FloatingPoint`. Also what is the benefit to using one over the other? – Noah Wilder May 11 '18 at 16:40
  • @NoahWilder Do you want your solutions to come out as integers? If no, use `FloatingPoint`. – Sweeper May 11 '18 at 16:41
  • @NoahWilder Also, your first bonus question could be asked as a separate question that could be interesting, given you've put some effort into asking it. For the second one, check this out: https://www.wikihow.com/Solve-a-Cubic-Equation – Sweeper May 11 '18 at 16:47
2

The key is to define / when T is FloatingPoint or BinaryInteger, and not just any Numeric. Furthermore, I would recommend you use an enum, to properly model the three possible outcomes of a quadratic function:

import Foundation

infix operator ± : AdditionPrecedence

public enum QuadraticRoots<T> {
    case two(T, T)
    case one(T)
    case none

    func applyWithSelfOnLeft(_ fn: (T, T) -> T, withOperand value: T) -> QuadraticRoots {
        switch self {
            case let .two(a, b): return .two(fn(a, value), fn(b, value))
            case let .one(a): return .one(fn(a, value))
            case .none: return .none
        }
    }

    func applyWithSelfOnRight(withOperand value: T, _ fn: (T, T) -> T) -> QuadraticRoots {
        switch self {
            case let .two(a, b): return .two(fn(value, a), fn(value, b))
            case let .one(a): return .one(fn(value, a))
            case .none: return .none
        }
    }
}

public func ± <T: Numeric>(left: T, right: T) -> QuadraticRoots<T> {
    return QuadraticRoots.two(left + right, left - right)
}

extension QuadraticRoots where T: Numeric {

    static func + (quadRoots: QuadraticRoots, rhs: T) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnLeft((+), withOperand: rhs)
    }
    static func - (quadRoots: QuadraticRoots, rhs: T) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnLeft((-), withOperand: rhs)
    }
    static func * (quadRoots: QuadraticRoots, rhs: T) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnLeft((*), withOperand: rhs)
    }


    static func + (lhs: T, quadRoots: QuadraticRoots) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnRight(withOperand: lhs, (+))
    }
    static func - (lhs: T, quadRoots: QuadraticRoots) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnRight(withOperand: lhs, (-))
    }
    static func * (lhs: T, quadRoots: QuadraticRoots) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnRight(withOperand: lhs, (*))
    }

    static func discriminantOf(xSquared a: T, x b: T, c: T) -> T { return b*b - 4*a*c }
}

extension QuadraticRoots where T: FloatingPoint {
    static func / (quadRoots: QuadraticRoots, rhs: T) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnLeft((/), withOperand: rhs)
    }

    static func / (lhs: T, quadRoots: QuadraticRoots) -> QuadraticRoots {
        return quadRoots.applyWithSelfOnRight(withOperand: lhs, (/))
    }

    static func solveFrom(xSquared a: T, x b: T, c: T) -> QuadraticRoots {
        let discriminant = self.discriminantOf(xSquared: a, x: b, c: c)
        return (-b ± sqrt(discriminant)) / (2 * a)
    }
}

let a = 5 ± 10
print(a)
print(a + 2)
print(a - 2)
print(a * 2)

print(2 + a)
print(2 - a)
print(2 * a)

//print(a / 2)
Alexander
  • 59,041
  • 12
  • 98
  • 151