Swift扩展协议处理跨类型运算
Swift扩展协议处理跨类型运算

Swift扩展协议处理跨类型运算

在 Swift 中,跨类型运算通常需要显式地将参与运算的变量进行类型转换。由于 Swift 是强类型语言,它要求所有的操作数具有明确的类型。以下是实现跨类型运算的方法:

1、手动类型转换

最基础的方法是通过显式转换来确保类型兼容。

let intValue: Int = 10
let doubleValue: Double = 2.5

// 显式转换 Int -> Double
let result = Double(intValue) * doubleValue
print(result) // 输出 25.0

2、重载运算符支持跨类型运算

通过扩展和重载运算符,可以为不同类型之间的运算提供直接支持。

示例

实现 Int 和 Double 的直接乘法。

extension BinaryInteger {
    static func * (lhs: Self, rhs: Double) -> Double {
        return Double(lhs) * rhs
    }

    static func * (lhs: Double, rhs: Self) -> Double {
        return lhs * Double(rhs)
    }
}

let intValue: Int = 10
let doubleValue: Double = 2.5

let result1 = intValue * doubleValue // 自动调用重载
let result2 = doubleValue * intValue // 自动调用重载

print(result1) // 输出 25.0
print(result2) // 输出 25.0

在协议扩展中,Self 表示扩展作用于的具体类型。例如:

如果 Self 是 Int,它代表 Int 类型。

如果 Self 是 UInt64,它代表 UInt64 类型。

3、使用通用协议

Swift 的 Numeric 协议和 BinaryInteger 协议可以用来处理跨类型运算。

示例

实现一个函数,支持任意符合 Numeric 协议的类型。

func add<T: Numeric, U: Numeric>(_ a: T, _ b: U) -> Double {
    return Double("\(a)")! + Double("\(b)")!
}

let result = add(5, 3.2) // Int 和 Double
print(result) // 输出 8.2

4、使用泛型和类型约束

为特定类型组合(如 Int 和 Double)实现泛型函数。

示例

func multiply<T: BinaryInteger, U: FloatingPoint>(_ int: T, _ double: U) -> U {
    return U(int) * double
}

let result = multiply(3, 2.5) // Int 和 Double
print(result) // 输出 7.5

5、Foundation 的 NSNumber

NSNumber 是一个通用的数字容器,可以存储多种类型(Int、Double 等),适用于动态类型场景。

示例

import Foundation

let intValue = NSNumber(value: 10)
let doubleValue = NSNumber(value: 2.5)

// 转换为 Double 并计算
let result = intValue.doubleValue * doubleValue.doubleValue
print(result) // 输出 25.0

注意事项

1、精度损失:跨类型运算可能导致精度损失,例如从浮点数转换到整数时会截断小数部分。

let result = Int(3.9) // result 为 3

2、隐式转换:Swift 不支持隐式类型转换,这与许多动态类型语言不同,强制开发者显式处理类型。

3、整数溢出:当整数类型之间进行运算时,如果结果超出范围,会导致溢出。

let bigInt: Int64 = Int64.max
let result = bigInt * 2 // 溢出,程序崩溃

通过显式转换和运算符重载,可以有效解决跨类型运算的需求。

扩展知识

跨类型运算导致的精度丢失问题

extension BinaryInteger {
    static func * (lhs: Self, rhs: Double) -> Double {
        return Double(lhs) * rhs
    }

    static func * (lhs: Double, rhs: Self) -> Double {
        return lhs * Double(rhs)
    }
}
let exampleInt: Int64 = 50_000_000_000_000_001
print(exampleInt)   // 输出:50000000000000001

let result = exampleInt * 1.0
print(String(format: "%.0f", result))   // 输出:50000000000000000

扩展BinaryInteger协议,计算整数和Double类型并返回一个Double类型的值,将exampleInt转换为Double,由于精度缺失,Double类型的值变为:50000000000000000。

问题的原因为

Int64 是一个 64 位的整型,能够精确表示范围在 -2^632^63 – 1 的所有整数。

Double 是一个 64 位的浮点数,使用 IEEE 754 标准表示,但它的有效精度是 53 位。因此,当整数的位数超过浮点数的精度时,可能会出现舍入或截断。

当将 exampleInt 转换为 Double 时,Double(lhs) 将尝试表示一个非常大的整数 50_000_000_000_000_001。

由于 Double 的有效精度限制,不能准确表示这个整数,因此它会被舍入为最接近的可表示值,也就是 50000000000000000。

String(format: "%.0f", result)

格式化输出了 result,%.0f 表示将 Double 按照四舍五入规则输出为没有小数点的形式。

由于之前已经在转换为 Double 时丢失了精度,result 的值已经是 50000000000000000,因此最终格式化输出为该值。

解决方法

1、避免转换为 Double

如果需要精确计算,应该避免将 Int64 转换为 Double,或者考虑使用 高精度整数类型(如 Decimal 或其他库支持的高精度类型)。

2、示例:使用 Decimal

import Foundation

let exampleInt: Int64 = 50_000_000_000_000_001
let decimalResult = Decimal(exampleInt) * Decimal(1.0)
print(decimalResult) // 精确输出

3、检查数值的合理范围

在设计代码时,确认是否需要处理如此大的数值。如果不需要,可以选择限制范围以避免精度丢失。

参考文章

1、Key points:https://www.hackingwithswift.com/guide/ios-swiftui/6/2/key-points

2、Swift高精度数据类型Decimal:https://fangjunyu.com/2024/12/04/swift%e9%ab%98%e7%b2%be%e5%ba%a6%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8bdecimal/

3、Swift封装基本数值的NSNumber类:https://fangjunyu.com/2024/12/04/swift%e5%b0%81%e8%a3%85%e5%9f%ba%e6%9c%ac%e6%95%b0%e5%80%bc%e7%9a%84nsnumber%e7%b1%bb/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注