01c606970d69b8662d204b6cea258317
WWDC20 10217 - 了解 Swift 中的数值计算

本文基于 WWDC20 - Explore numerical computing in Swift

Swift Numerics

Numerics 是一个 Apple 开源的 Swift 包,通过范型约束,提供更简单的方式,来使用所有标准库里的浮点型进行数值计算。
下面通过一个例子来看下这个包的作用。比如我们要在 Swift 中实现一个 Logit 模型 的函数,在没有 Numerics 的情况下:

import Darwin
/// Logit 模型
///
/// https://en.wikipedia.org/wiki/Logit
///
/// - 参数 p:
///   取值范围 0...1。
///
/// - 返回值:
///   log(p/(1-p))。
func logit(_ p: Double) -> Double {
    log(p) - log1p(-p)
}

为了实现 log(p/(1-p)),我们需要调用 Darwin 里的 loglog1p,这两个函数位于 Darwin.C 中,是 C 标准库所定义的接口,里面用一系列同名函数来支持不同的具体浮点型。当我们用这类函数编写功能时,为了支持所有的浮点型(DoubleFloatFloat80 以及后续标准库可能增加的类型)就需要将重复的代码拷贝多次,大大提高了维护成本。

这时候可能你会想,要是能使用范型来代替这里面具体的浮点型就好了,这时候 Numerics 就派上用场了。

Real 协议

Numerics 里面提供了一个全新的 Real 协议,对这类计算的类型提供支持。通过 Real 协议,上面的例子可以改造成:

import Numerics

func logit<NumberType: Real>(_ p: NumberType) -> NumberType {
    .log(p) - .log(onePlus: -p)
}

NumberType 范型增加 Real 协议约束,并将 loglog1p 函数替换成 Numerics 里支持范型的 loglog(one plus:) 版本。所有浮点型都会遵循 Real 协议,这个改写后的 logit 函数,不仅能根据平台支持其对应的浮点型参数,在以后标准库增加新的浮点型时,也无需做额外的适配。

public protocol Real: FloatingPoint, RealFunctions, AlgebraicField {
}

Real 协议是一个协议组合,其中 FloatingPoint 协议是标准库中的协议,其余两个协议是 Numerics 里所提供的新协议。这里需要注意的是,对于开发者而言,只应该使用 Real 协议本身

先来看看目前 Swift 标准库里已经存在关于数值的协议:

我们这里只关心其中关键的一部分:

  • AdditiveArithmetic:用于支持加减法的类型,包括了大部分应该属于“数字”的概念,和数学领域的“代数群”几乎吻合。
  • SignedNumeric:拓展了乘法概念。
  • FloatingPoint:拓展了计算机中浮点型实现所需要的各种概念,比如比较、幂运算和有效位数等,还有各种常用的变量 infinity(∞)、nanpi 等。

而 Numerics 是基于这些核心概念来构建的。

AlgebraicField 协议

public protocol AlgebraicField: SignedNumeric {
  static func /(a: Self, b: Self) -> Self

  /// 倒数
  var reciprocal: Self? { get }

  /// ...
}

SignedNumeric 的基础上拓展了除法概念。这样就支持了全部四则运算,数学领域称为”代数数域“,这也是这个协议名字的由来。

ElementaryFunctions 协议

public protocol ElementaryFunctions: AdditiveArithmetic {
  /// 指数
  static func exp(_ x: Self) -> Self

  /// exp(x) - 1
  static func expMinusOne(_ x: Self) -> Self

  /// 三角函数
  static func cos(_ x: Self) -> Self
  static func sin(_ x: Self) -> Self
  static func tan(_ x: Self) -> Self

  /// 对数
  static func log(_ x: Self) -> Self

    /// log(1 + x)
  static func log(onePlus x: Self) -> Self

  /// exp(y * log(x)) 
  static func pow(_ x: Self, _ y: Self) -> Self

  /// 幂
  static func pow(_ x: Self, _ n: Int) -> Self

  /// 次方根
  static func root(_ x: Self, _ n: Int) -> Self

  /// ...
}

AdditiveArithmetic 的基础上拓展了大量通用的浮点型函数,包括核心的三角函数、指数、对数、幂和次方根等。

RealFunctions 协议

public protocol RealFunctions: ElementaryFunctions {
  /// 误差函数
  static func erf(_ x: Self) -> Self

  /// sqrt(x*x + y*y)
  static func hypot(_ x: Self, _ y: Self) -> Self

  /// Γ(x)
  static func gamma(_ x: Self) -> Self

  /// log(|Γ(x)|)
  static func logGamma(_ x: Self) -> Self

  /// ...
}

ElementaryFuctions 的基础上拓展了更多类似但少用的函数,比如伽马函数、误差函数和更多底数的指数和对数等。

组合而成的 Real 协议因此巧妙地定义了标准浮点型所应该有的通用功能。这就是 Numerics 是如何将标准浮点型变得更加有用和优雅的。

虽然 Real 协议的概念很简单,但在实践中却格外强大。

  • 范型支持

  • 解决重复的代码

  • 更低的维护成本

  • 更好的兼容性(支持新的浮点型)

Complex 类型

Complex 类型是 Numerics 中的一部分,为 Swift 提供了复数支持,且是使用 Real 协议作为范型约束的。

```swift
import Numerics

top Created with Sketch.