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 BinaryFloatingPoint
s and BinaryInteger
s to Float
, because those protocols define the necessary properties such that Float.init
can understand how they are represented. For example, BinaryInteger
s are represented by words
, which is a RandomAccessCollection
of UInt
s, indexed by Int
s.
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
!