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!