Swift Numerics gives generic code a mathematical contract
Swift Numerics launches with Real and Complex modules. It makes mathematical operations available through generic constraints and gives numerical APIs room to develop outside the standard library.
A generic floating-point function needs more than arithmetic if its formula contains a logarithm or a sine. Swift's existing floating-point protocols do not provide the complete mathematical contract such a function needs. Steve Canon announced Swift Numerics on November 7 to fill that gap through a package, initially containing Real and Complex modules.
The package also resolves a distribution problem. The accepted generic-math proposal cannot yet enter the standard library in a source-stable way because of compiler limitations. A separate module can supply the operations immediately. Its release schedule can also differ from Swift's, leaving room to experiment before an API becomes a permanent language-library commitment.
I find this a sensible use of Swift's type system. A numerical algorithm should state the operations it requires without forcing every input through one concrete representation. The resulting generic constraint is useful documentation as well as something the compiler can check.
Floating-point arithmetic is not the whole requirement
The earlier SE-0246 proposal under review in March identifies the original limitation. Platform math functions are available as concrete overloads, while a generic parameter constrained to floating-point arithmetic does not automatically gain those operations. Importing the appropriate platform module also introduces platform-specific source decisions.
Converting an input to Double can make one concrete overload available, but it changes the numerical computation. If the original type has less precision, the conversion adds work the algorithm did not require. If it has greater precision, the intermediate representation discards information. Converting the result back cannot reconstruct information already lost.
This matters even when the formula is short. Generic syntax should not suggest that the calculation preserves the chosen numeric type if its implementation silently routes through a fixed one. A constraint that includes the required mathematical operations lets the implementation keep that choice explicit throughout the formula.
The launched package uses Real for floating-point types equipped with the basic mathematical operations. An elementary example can therefore name its actual requirements.
import Numerics
func sineSquared<T: Real>(_ x: T) -> T {
let value = T.sin(x)
return value * value
}
The function needs sine and multiplication, so its constraint supplies both. It does not claim that every possible numeric type supports sine with an appropriate result of the same type. Choosing a narrower contract prevents a superficially general signature from concealing assumptions in its body.
Protocol boundaries follow mathematical domains
The initial API distinguishes ElementaryFunctions, RealFunctions, and Real. The first supplies elementary operations with a broader mathematical meaning. Real-specific functions belong to a refinement, while Real combines the floating-point and mathematical capabilities commonly needed by generic real-valued algorithms.
That distinction becomes useful when complex numbers enter the design. Some mathematical operations have a natural complex interpretation, while others depend on real-specific properties or implementations. A single protocol containing every operation would make conformance promise more than some types can appropriately supply.
A protocol hierarchy can therefore express domain information that a collection of unrelated overloads leaves implicit. An algorithm choosing a real-specific operation declares a correspondingly real-specific requirement. A more general operation can use the smaller contract when it does not depend on that additional structure.
For a reader maintaining generic code, this also makes changes to a constraint meaningful. Requiring a broader protocol is a change to the set of types the function accepts. An implementation that adds a real-specific operation may need to narrow that set, even when its parameter and return names stay unchanged. The compiler can expose that dependency instead of leaving it in documentation alone.
The hierarchy does not settle the numerical quality of every algorithm built on it. A type can provide the required operations while a formula still has poor behavior for particular magnitudes. The generic contract states which operations exist. The algorithm remains responsible for using them in a way appropriate to its inputs.
Complex numbers need a stated exceptional-value model
The initial Complex type is generic over a real floating-point type. Its representation has real and imaginary components, and its arithmetic uses the usual operators. A short example makes the ordinary multiplication rule concrete.
import Complex
let first = Complex<Double>(1, 2)
let second = Complex<Double>(3, -1)
let product = first * second
Algebraically the product has real part five and imaginary part five. That follows by expanding the multiplication and using the imaginary unit squared as negative one. Keeping the operation as multiplication of complex values communicates this relationship more directly than separately maintaining the component formulas throughout application code.
The announcement also makes an explicit choice about non-finite values. Complex values with non-finite components collapse into one point at infinity. That differs from preserving finer distinctions among combinations of infinities and NaNs. Canon explains the choice as a tradeoff that permits simpler and faster arithmetic for the intended model.
This is a consequential API decision because exceptional values participate in observable behavior. An application expecting distinctions from another complex-number implementation cannot infer equivalent behavior merely from matching operator names. A numerical type's contract includes its exceptional cases as well as the ordinary arithmetic examples.
I appreciate the explicitness here. A mathematical library should document the model it implements so that an algorithm can depend on it deliberately. Preserving every distinction would also be a policy with costs, and hiding that policy would make comparison between implementations harder.
Optional results can describe numerical applicability
The announcement uses the optional reciprocal property to choose an optimization when the divisor is appropriately scaled. When a reciprocal is available, repeated divisions can become multiplications. Otherwise the code retains division. The optional communicates whether that alternative is suitable under the implementation's conditions.
That is a useful role for absence in a numeric API. A missing reciprocal does not require inventing a plausible value and hoping a caller recognizes it as a sentinel. The caller handles the alternative explicitly and can preserve a valid fallback operation.
Package delivery also makes these design choices easier to develop without requiring every Swift program to import them. Numerical code can depend on the relevant modules, while unrelated applications retain their existing dependencies. Canon identifies further numerical work as plans, not as functionality already present. For a library author adopting the initial package, the concrete starting point is generic real arithmetic and complex values with the documented behavior above.