Swift Digest
Blog | Swift.org Blog

Swift Numerics

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

何が発表されたのか

Swift Numerics は、Swift で数値計算を行うための土台を提供するオープンソースパッケージです。標準ライブラリの既存 API では埋まっていなかった重要な穴を埋め、Swift で新たな分野のプログラミングを可能にすることを目的としています。

発表時点では、計算数学ですぐに役立つ 2 つのモジュールが用意されました。

Real モジュール

SE-0246 は、サイン(sin)や対数(log)といった操作をジェネリックな文脈で使えるようにする「基本数学関数」の API を提案し、受理されました。しかし当時はコンパイラの制約により、ソース安定性を保ったまま標準ライブラリに追加することができませんでした。Real モジュールはこの API を独立したモジュールとして提供することで、その改善された API をプロジェクトですぐに使えるようにします。

このモジュールは 3 つのプロトコルを定義します。

たとえば、ジェネリックなシグモイド関数は次のように書けます。

import Numerics

func sigmoid<T: Real>(_ x: T) -> T {
  1 / (1 + .exp(-x))
}

このコードは FloatDouble、(ターゲットが対応していれば)Float80 で動作します。将来 Float16Float128 のような新しい浮動小数点型が Swift に追加されれば、それらでも動作するようになります。

Complex モジュール

Complex モジュールは Real を土台にして、Swift に複素数型を提供します。複素数は、フーリエ変換をはじめとする信号処理など、多くのアルゴリズムを自然に記述できる場であり、多くの言語や標準ライブラリが提供する重要な基本要素です。

Complex 型は、Real に適合する RealType をジェネリックパラメータに取ります。

public struct Complex<RealType> where RealType: Real {
  ...
}

複素数は実部と虚部の 2 つの成分を持ち、虚数単位 i を使って数学では a + bi と書きます。Swift でも同様に書けます。

let z: Complex<Double> = 2 + 3 * .i
print(z.real)      // 2.0
print(z.imaginary) // 3.0

実部と虚部を指定して構築することもできます。表示は Fortran スタイルで、a + bi(a, b) と出力されます。

let w = Complex<Double>(1, -2) // (1.0, -2.0)
print(z) // (2.0, 3.0)

Complex 型は Numeric プロトコルに適合し、通常の演算子で加減乗除を行えます。割り算も通常の / 演算子を使うため、ほかの数値型と同じように扱えます。

func scaleAndRotate<T>(_ z: Complex<T>) -> Complex<T> {
  z * Complex(0, 2)
}

let result = scaleAndRotate(Complex(1.0, 1.0)) // (-2.0, 2.0)

無限大や NaN の扱いについては、C や C++ の複素数ライブラリが異なるゼロ・無限大・NaN を細かく区別しようとするのに対し、Swift Numerics はこの区別をしません。実部と虚部がともにゼロなら 0、実部か虚部のいずれかが非有限ならすべて単一の「無限遠点」に集約されます。情報がわずかに失われますが、この区別を活用するプログラムはごくわずかである一方、区別を維持しようとするとすべてのプログラムの性能が損なわれるためです。

この設計により、Swift Numerics の複素数演算は C より高速です。値が適切にスケールされている一般的なケースで乗算は約 30% 速く、除算では条件次第でさらに大きな差が出ます。除算演算がコンパイラから見える形で実装されているため、多数の値を 1 つの除数で割るような場面で最適化の余地が大きくなります。除数が適切にスケールされている場合は、次のように reciprocal プロパティを使って乗算に置き換えることで、さらに高速化できます。

// 除数が適切にスケールされていれば、除算ではなく逆数の乗算を使う。
if let recip = divisor.reciprocal {
  return data.map { $0 * recip }
}
// そうでなければ除算にフォールバックする。
return data.map { $0 / divisor }

さらに Swift Numerics は、扱いの難しいケースでも C や Python より正確な結果を返します。

なぜパッケージなのか

著者は、すべてを標準ライブラリに入れるべきではないという考えから、この機能をパッケージとして開発しています。SwiftNIO がネットワーク分野で果たしているのと同じように、数値計算を中心としたモジュールの共通の置き場所を提供することが狙いです。パッケージにすることで次の利点もあります。

一部のモジュールはいずれ標準ライブラリへ取り込まれる可能性がありますが、すべてのプロジェクトにデフォルトで含まれるべきではないモジュールには、別の住処が必要だという位置づけです。

今後の見通し

発表時点では、今後数か月のうちに次のような機能を追加していく計画が示されていました。いずれも将来の構想であり、実現を約束するものではありません。

また、ComplexElementaryFunctions に適合させ、複素数でも一連の超越関数を使えるようにする作業も進められていました。

関連リンク