Melodius
Melodius

Reputation: 2795

How can you create a generic struct for Numeric numbers that can calculate a percentage?

This doesn't compile because Initializer 'init(_:)' requires that 'Number' conform to 'BinaryInteger'

struct Percentage<Number: Numeric> {
    let value: Number
    let total: Number
    var percentage: Double {
        Double(value) / Double(total)
    }
}

Does anyone have a nice solution?

To give some context to the problem from real life: I'm coding a SwiftUI app, that has a CircularProgress-view. I would like to use the same CircularProgress-view with different number types and to be able to show the current value in proportion to min and max. To do that, I need to solve the problem above.

Upvotes: 0

Views: 264

Answers (2)

David Pasztor
David Pasztor

Reputation: 54745

You can create extensions on Percentage where you restrict Number to BinaryInteger and FloatingPoint to be able to use the / operator.

struct Percentage<Number: Numeric> {
    let value: Number
    let total: Number
}

extension Percentage where Number: BinaryInteger {
    var percentage: Double {
        Double(value) / Double(total)
    }
}

extension Percentage where Number: FloatingPoint {
    var percentage: Number {
        value / total
    }
}

Percentage(value: 15, total: 60).percentage // 25.0
Percentage(value: 1.5, total: 3.0).percentage // 50.0

Upvotes: 3

Leo Dabus
Leo Dabus

Reputation: 236458

The main issue is that Numeric doesn't support generic divisions. One possible solution is to provide multiple generic methods to support different protocols (Integer/FloatingPoint) and Decimal as well:

extension Decimal {
    var number: NSDecimalNumber { self as NSDecimalNumber }
    var double: Double { number.doubleValue }
}

struct Percentage<T: Numeric> {
    let value: T
    let total: T
    func percentage<F: BinaryFloatingPoint>() -> F where T: BinaryFloatingPoint {
        F(value) / F(total)
    }
    func percentage<F: BinaryFloatingPoint>() -> F where T: BinaryInteger {
        F(value) / F(total)
    }
    func percentage<F: BinaryFloatingPoint>() -> F where T == Decimal {
        F(value.double) / F(total.double)
    }
    func percentage() -> Decimal where T == Decimal {
        value / total
    }
}

let percentageInt = Percentage<Int>(value: 10, total: 100)
let result1: Double = percentageInt.percentage()   // 0.1

let percentageDouble = Percentage<Double>(value: 10, total: 100)
let result2: Double = percentageDouble.percentage()   // 0.1
let result3: CGFloat = percentageDouble.percentage()   // 0.1

let percentageDecimal = Percentage<Decimal>(value: 10, total: 100)
let result4 = percentageDecimal.percentage()     // 0.1 decimal
let result5: Double = percentageDecimal.percentage()   // 0.1

Upvotes: 3

Related Questions