0

I'm creating some utility class stores numeric values(Int, Float, Double etc...). It has minimum and maximum value, too.

I need convert a value to percent.

So I write the class as below on Playground:

import UIKit

class Series<T: SignedNumeric> {
    let minValue: T
    let maxValue: T
    var datas: [T] = []

    let range: T

    init(minValue: T, maxValue: T) {
        self.minValue = minValue
        self.maxValue = maxValue
        self.range = (maxValue - minValue)
        self.datas = []
    }

    func percent(value: T) -> Float {
        let v: Float = value as! Float
        let m: Float = minValue as! Float
        let r: Float = range as! Float
        return (v - m) / r
    }
}

let s = Series<Int>(minValue: -100, maxValue: 100)
s.datas = [20, 0, 40, -100, 100]
Float(s.datas[0] - s.minValue) / Float(s.range)
Float(s.datas[1] - s.minValue) / Float(s.range)
Float(s.datas[2] - s.minValue) / Float(s.range)
Float(s.datas[3] - s.minValue) / Float(s.range)
Float(s.datas[4] - s.minValue) / Float(s.range)
s.percent(value: s.datas[0]) // ERROR !!!

When call s.percent(value: s.datas[0]), it crashed.

How can I write percent() function?

2 Answers2

1

Not every SignedNumeric can be converted to a Float. Recall that to be a SignedNumeric, you need to be able to:

  • be negative and positive
  • be multiplied
  • be added and subtracted
  • have a magnitude that is Comparable and Numeric
  • can be converted from an integer

The last 4 requirements are all inherited from Numeric. We can easily build our own type that can do all those things. Here's a contrived example:

struct MyNumber: SignedNumeric, Comparable {
    private var secret: [String]
    private var isNegative: Bool
    private var number: Int { secret.count }
    
    static func *= (lhs: inout MyNumber, rhs: MyNumber) {
        lhs = lhs * rhs
    }
    
    static func * (lhs: MyNumber, rhs: MyNumber) -> MyNumber {
        MyNumber(secret: Array(repeating: "", count: lhs.number * rhs.number), isNegative: lhs.isNegative != rhs.isNegative)
    }
    
    init(integerLiteral value: Int) {
        let int = value
        isNegative = int < 0
        secret = Array(repeating: "", count: abs(int))
    }
    
    static func < (lhs: MyNumber, rhs: MyNumber) -> Bool {
        lhs.number < rhs.number
    }
    
    init?<T>(exactly source: T) where T : BinaryInteger {
        guard let int = Int(exactly: source) else {
            return nil
        }
        self.init(integerLiteral: int)
    }
    
    var magnitude: MyNumber {
        MyNumber(secret: secret, isNegative: false)
    }
    
    static func - (lhs: MyNumber, rhs: MyNumber) -> MyNumber {
        if lhs < rhs {
            return -(rhs - lhs)
        } else {
            return MyNumber(secret: Array(repeating: "", count: lhs.number - rhs.number))
        }
    }
    
    prefix static func - (operand: MyNumber) -> MyNumber {
        MyNumber(secret: operand.secret, isNegative: !operand.isNegative)
    }
    
    mutating func negate() {
        isNegative.toggle()
    }
    
    init(secret: [String], isNegative: Bool = false) {
        self.secret = secret
        self.isNegative = isNegative
    }
    
    typealias Magnitude = MyNumber
    
    static func + (lhs: MyNumber, rhs: MyNumber) -> MyNumber {
        MyNumber(secret: lhs.secret + rhs.secret)
    }
    
    typealias IntegerLiteralType = Int
    
}

How on earth is <insert whoever is doing the conversion here> going to know that, to convert MyNumber to Float, it has to do Float(aMyNumber.secret.count)!? Not to mention that secret is private.

On the other hand, Float.init does know how to convert BinaryFloatingPoints and BinaryIntegers to Float, because those protocols define the necessary properties such that Float.init can understand how they are represented. For example, BinaryIntegers are represented by words, which is a RandomAccessCollection of UInts, indexed by Ints.

I suggest that you only add the percent method to types of Series where it actually makes sense:

extension Series where T : BinaryInteger {
    func percent(value: T) -> Float {
        let v: Float = Float(value)
        let m: Float = Float(minValue)
        let r: Float = Float(range)
        return (v - m) / r
    }
}

IMO, it would make sense for any T : FloatingPoint, not just T : BinaryFloatingPoint:

extension Series where T : FloatingPoint {
    func percent(value: T) -> T {
        let v = value
        let m = minValue
        let r = range
        return (v - m) / r
    }
}

Since FloatingPoint supports division too, you don't need to convert to Float!

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Hello, thank you very much! I added two extensions: ``` extension Series where T : BinaryInteger { func percent(value: T) -> Float { let v: Float = Float(value) let m: Float = Float(minValue) let r: Float = Float(range) return (v - m) / r } } extension Series where T : FloatingPoint { func percent(value: T) -> T { let v = value let m = minValue let r = range return (v - m) / r } } ``` And now I can use percent with Int, Float, Int16... ! – Shuji Fukumoto Jan 17 '21 at 11:50
  • https://stackoverflow.com/questions/65758876/how-can-i-convert-generic-type-signednumeric-to-float#comment116269964_65760275 – Leo Dabus Jan 17 '21 at 14:06
0

thank you very much!

I added two extensions:

extension Series where T : BinaryInteger {
    func percent(value: T) -> Float {
        let v: Float = Float(value)
        let m: Float = Float(minValue)
        let r: Float = Float(range)
        return (v - m) / r
    }
}

extension Series where T : FloatingPoint {
    func percent(value: T) -> T {
        let v = value
        let m = minValue
        let r = range
        return (v - m) / r
    }
}

And now I can use percent() with Int, Float, Int16... !

  • Yes but your percent method when used with integers can only return `Float`. You should make it generic and return any `FloatingPoint` type `func percent(value: T) -> F { (F(value) - F(minValue)) / F(range) }` – Leo Dabus Jan 17 '21 at 14:04
  • Thanks for your comment. But I have to learn about Generics much more :-) – Shuji Fukumoto Jan 18 '21 at 06:12
  • https://stackoverflow.com/questions/65737343/how-can-you-create-a-generic-struct-for-numeric-numbers-that-can-calculate-a-per/65738047#65738047 – Leo Dabus Jan 18 '21 at 06:16