首页 > 解决方案 > 如何将通用类型 SignedNumeric 转换为 Float?

问题描述

我正在创建一些实用程序类存储数值(Int、Float、Double 等......)。它也有最小值和最大值。

我需要将值转换为百分比。

所以我在操场上写了如下课程:

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 !!!

当调用 s.percent(value: s.datas[0]) 时,它崩溃了。

如何编写 percent() 函数?

标签: swiftgenerics

解决方案


并非每个SignedNumeric都可以转换为Float. 回想一下,要成为SignedNumeric,您需要能够:

  • 消极和积极
  • 倍增
  • 被加减
  • 有一个幅度是ComparableNumeric
  • 可以从整数转换

最后 4 个需求都继承自Numeric. 我们可以轻松地构建自己的类型来完成所有这些事情。这是一个人为的例子:

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
    
}

<insert whoever is doing the conversion here>到底怎么会知道,要转换MyNumberFloat,它必须这样做Float(aMyNumber.secret.count)!?更不用说secret是私人的了。

另一方面,Float.init确实知道如何将BinaryFloatingPoints 和BinaryIntegers 转换为Float,因为这些协议定义了必要的属性,以便Float.init可以理解它们的表示方式。例如,BinaryIntegers 由 s 表示words,它是 s 的一个RandomAccessCollectionUInt由 s 索引Int

我建议您只将该percent方法添加到Series它真正有意义的类型中:

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,这对任何人都有意义T : FloatingPoint,而不仅仅是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
    }
}

由于FloatingPoint也支持除法,因此您无需转换为Float!


推荐阅读