Skip to content

Commit 5d84256

Browse files
committed
Initial work on docc setup.
1 parent 6356eb3 commit 5d84256

File tree

7 files changed

+378
-0
lines changed

7 files changed

+378
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ``Complex``
2+
3+
## Topics
4+
5+
### Real and imaginary parts
6+
7+
A `Complex` value is represented with two `RealType` values, corresponding to
8+
the real and imaginary parts of the number:
9+
```swift
10+
let z = Complex(1,-1) // 1 - i
11+
let re = z.real // 1
12+
let im = z.imaginary // -1
13+
```
14+
All `Complex` numbers with a non-finite component is treated as a single
15+
"point at infinity," with infinite magnitude and indeterminant phase. Thus,
16+
the real and imaginary parts of an infinity are nan.
17+
```swift
18+
let w = Complex<Double>.infinity
19+
w == -w // true
20+
let re = w.real // .nan
21+
let im = w.imag // .nan
22+
```
23+
See <doc:Infinity> for more details.
24+
25+
- ``init(_:_:)``
26+
- ``init(_:)-5aesj``
27+
- ``init(imaginary:)``
28+
- ``real``
29+
- ``imaginary``
30+
31+
### Magnitude and norms
32+
33+
See the article <doc:Magnitude> for more details.
34+
35+
- ``magnitude``
36+
- ``length``
37+
- ``lengthSquared``
38+
- ``normalized``
39+
40+
### Polar representations
41+
42+
- ``init(length:phase:)``
43+
- ``phase``
44+
- ``length``
45+
- ``polar``
46+
47+
### Conversions from other types
48+
49+
- ``init(_:)-4csd3``
50+
- ``init(_:)-80jml``
51+
- ``init(exactly:)-767k9``
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# ``ComplexModule``
2+
3+
Types and operations for working with complex numbers.
4+
5+
## Representation
6+
7+
The `Complex` type is generic over an associated `RealType`; complex numbers
8+
are represented as two `RealType` values, the real and imaginary parts of the
9+
number.
10+
```
11+
let z = Complex<Double>(1, 2)
12+
let re = z.real
13+
let im = z.imaginary
14+
```
15+
16+
### Memory layout
17+
18+
A `Complex` value is stored as two `RealType` values arranged consecutively
19+
in memory. Thus it has the same memory layout as:
20+
- A Fortran complex value built on the corresponding real type (as used
21+
by BLAS and LAPACK).
22+
- A C struct with real and imaginary parts and nothing else (as used by
23+
computational libraries predating C99).
24+
- A C99 `_Complex` value built on the corresponding real type.
25+
- A C++ `std::complex` value built on the corresponding real type.
26+
Functions taking complex arguments in these other languages are not
27+
automatically converted on import, but you can safely write shims that
28+
map them into Swift types by converting pointers.
29+
30+
## Real-Complex arithmetic
31+
32+
Because the real numbers are a subset of the complex numbers, many
33+
languages support arithmetic with mixed real and complex operands.
34+
For example, C allows the following:
35+
```c
36+
#include <complex.h>
37+
double r = 1;
38+
double complex z = CMPLX(0, 2); // 2i
39+
double complex w = r + z; // 1 + 2i
40+
```
41+
The `Complex` type does not provide such mixed operators. There are two
42+
reasons for this choice. First, Swift generally avoids mixed-type
43+
arithmetic, period. Second, mixed-type arithmetic operators lead to
44+
undesirable behavior in common expressions when combined with literal
45+
type inference. Consider the following example:
46+
```swift
47+
let a: Double = 1
48+
let b = 2*a
49+
```
50+
If we had a heterogeneous `*` operator defined, then if there's no prevailing
51+
type context (i.e. we aren't in an extension on some type), the expression
52+
`2*a` is ambiguous; `2` could be either a `Double` or `Complex<Double>`. In
53+
a `Complex` context, the situation is even worse: `2*a` is inferred to have
54+
type `Complex`.
55+
56+
Therefore, the `Complex` type does not have these operators. In order to write
57+
the example from C above, you would use an explicit conversion:
58+
```swift
59+
import ComplexModule
60+
let r = 1.0
61+
let z = Complex<Double>(0, 2)
62+
let w = Complex(r) + z
63+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Zero and infinity
2+
3+
Semantics of `Complex` zero and infinity values, and important considerations
4+
when porting code from other languages.
5+
6+
Unlike C and C++'s complex types, `Complex` does not attempt to make a
7+
semantic distinction between different infinity and NaN values. Any `Complex`
8+
datum with a non-finite component is treated as the "point at infinity" on
9+
the Riemann sphere--a value with infinite magnitude and unspecified phase.
10+
11+
As a consequence, all values with either component infinite or NaN compare
12+
equal, and hash the same. Similarly, all zero values compare equal and hash
13+
the same.
14+
15+
## Rationale
16+
17+
This decision has some drawbacks,¹ but also some significant advantages.
18+
In particular, complex multiplication is one of the most common operations with
19+
a complex type, and one would like to be able to use the usual naive arithmetic
20+
implementation, consisting of four real multiplications and two real additions:
21+
```
22+
(a + bi) * (c + di) = (ac - bd) + (ad + bc)i
23+
```
24+
`Complex` can use this implementation, because we do not differentiate between
25+
infinities and NaN values. C and C++, by contrast, cannot use this
26+
implementation by default, because, for example:
27+
```
28+
(1 + ∞i) * (0 - 2i) = (1*0 - ∞*(-2)) + (1*(-2) + ∞*0)i
29+
= (0 - ∞) + (-2 + nan)i
30+
= -∞ + nan i
31+
```
32+
`Complex` treats this as "infinity", which is the correct result. C and C++
33+
treat it as a nan value, however, which is incorrect; infinity multiplied
34+
by a non-zero number should be infinity. Thus, C and C++ (by default) must
35+
detect these special cases and fix them up, which makes multiplication a
36+
more computationally expensive operation.²
37+
38+
### Footnotes:
39+
¹ W. Kahan, Branch Cuts for Complex Elementary Functions, or Much Ado
40+
About Nothing's Sign Bit. In A. Iserles and M.J.D. Powell, editors,
41+
_Proceedings The State of Art in Numerical Analysis_, pages 165–211, 1987.
42+
43+
² This can be addressed in C programs by use of the `STDC CX_LIMITED_RANGE`
44+
pragma, which instructs the compiler to simply not care about these cases.
45+
Unfortunately, this pragma is not often used in real C or C++ programs
46+
(though it does see some use in _libraries_). Programmers tend to specify
47+
`-ffast-math` or maybe `-ffinite-math-only` instead, which has other
48+
undesirable consequences.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Magnitude and norms
2+
3+
Introduction to the concept of norm and discussion of the `Complex` type's
4+
`.magnitude` property.
5+
6+
In mathematics, a *norm* is a function that gives each element of a vector
7+
space a non-negative length.¹
8+
9+
Many different norms can be defined on the complex numbers, viewed as a
10+
vector space over the reals. All of these norms have some basic
11+
properties. If we use *‖z‖* to represent any norm of *z*, it must:
12+
- be *subadditive* (a.k.a. satisfy the triangle inequalty):
13+
‖z + w‖ ≤ ‖z‖ + ‖w‖ for any two complex numbers z and w.
14+
- be *homogeneous*
15+
‖az‖ = |a|‖z‖ for any real number a and complex number z.
16+
- and be *positive definite*
17+
‖z‖ is zero if and only if z is zero.
18+
19+
The three most commonly-used norms are:
20+
- 1-norm ("taxicab norm"):
21+
‖x + iy‖₁ = |x| + |y|
22+
- 2-norm ("Euclidean norm"):
23+
‖x + iy‖₂ = √(x² + y²)
24+
- ∞-norm ("maximum norm" or "Чебышёв [Chebyshev] norm")²:
25+
‖x + iy‖ = max(|x|,|y|)
26+
27+
> Exercise:
28+
> 1. Check that these properties hold for one of the three norms
29+
that we just defined. (Hint: write z = a+bi and w = c+di,
30+
and use the fact that the absolute value is a norm on the
31+
real numbers, and therefore has the same property).
32+
33+
The `Complex` type gives special names to two of these norms; `length`
34+
for the 2-norm, and `magnitude` for the ∞-norm.
35+
36+
## Magnitude:
37+
38+
The `Numeric` protocol requires us to choose a norm to call `magnitude`,
39+
but does not give guidance as to which one we should pick. The easiest choice
40+
might have been the Euclidean norm; it's the one with which people are most
41+
likely to be familiar.
42+
43+
However, there are good reasons to make a different choice:
44+
- Computing the Euclidean norm requires special care to avoid spurious
45+
overflow/underflow (see implementation notes for `length` below). The
46+
naive expressions for the taxicab and maximum norm always give the best
47+
answer possible.
48+
- Even when special care is used, the Euclidean and taxicab norms are
49+
not necessarily representable. Both can be infinite even for finite
50+
numbers.
51+
```swift
52+
let big = Double.greatestFiniteMagnitude
53+
let z = Complex(big, big)
54+
```
55+
The taxicab norm of `z` would be `big + big`, which overflows; the
56+
Euclidean norm would be `sqrt(2) * big`, which also overflows.
57+
58+
But the maximum norm is always equal to the magnitude of either `real`
59+
or `imaginary`, so it is necessarily representable if `z` is finite.
60+
- The ∞-norm is the choice of established computational libraries, like
61+
BLAS and LAPACK.
62+
63+
For these reasons, the `magnitude` property of `Complex` binds the
64+
maximum norm:
65+
```swift
66+
Complex(2, 3).magnitude // 3
67+
Complex(-1, 0.5).magnitude // 1
68+
```
69+
70+
## Length:
71+
72+
The `length` property of a `Complex` value is its Euclidean norm.
73+
74+
```swift
75+
Complex(2, 3).length // 3.605551275463989
76+
Complex(-1, 0.5).length // 1.118033988749895
77+
```
78+
79+
Aside from familiarity, the Euclidean norm has one important property
80+
that the maximum norm lacks:
81+
- it is *multiplicative*:
82+
‖zw‖₂ = ‖z‖₂‖w‖₂ for any two complex numbers z and w.
83+
84+
> Exercises:
85+
> 1. Why isn't the maximum norm multiplicative?
86+
(Hint: Let `z = Complex(1,1)`, and consider `z*z`.)
87+
> 2. Is the 1-norm multiplicative?
88+
89+
### Implementation notes:
90+
91+
The `length` property takes special care to produce an accurate answer,
92+
even when the value is poorly-scaled. The naive expression for `length`
93+
would be `sqrt(x*x + y*y)`, but this can overflow or underflow even when
94+
the final result should be a finite number.
95+
```swift
96+
// Suppose that length were implemented like this:
97+
extension Complex {
98+
var naiveLength: RealType {
99+
.sqrt(real*real + imaginary*imaginary)
100+
}
101+
}
102+
103+
// Then taking the length of even a modestly large number:
104+
let z = Complex<Float>(1e20, 1e20)
105+
// or small number:
106+
let w = Complex<Float>(1e-24, 1e-24)
107+
// would overflow:
108+
z.naiveLength // Inf
109+
// or underflow:
110+
w.naiveLength // 0
111+
```
112+
Instead, `length` is implemented using a two-step algorithm. First we
113+
compute `lengthSquared`, which is `x*x + y*y`. If this is a normal
114+
number (meaning that no overflow or underflow has occured), we can safely
115+
return its square root. Otherwise, we redo the computation with a more
116+
careful computation, which avoids spurious under- or overflow:
117+
```swift
118+
let z = Complex<Float>(1e20, 1e20)
119+
let w = Complex<Float>(1e-24, 1e-24)
120+
z.length // 1.41421358E+20
121+
w.length // 1.41421362E-24
122+
```
123+
124+
### Footnotes:
125+
126+
¹ Throughout this documentation, "norm" refers to a
127+
[vector norm](https://en.wikipedia.org/wiki/Norm_(mathematics)).
128+
To confuse the matter, there are several similar things also called
129+
"norm" in mathematics. The other one you are most likely to run into
130+
is the [field norm](https://en.wikipedia.org/wiki/Field_norm).
131+
132+
Field norms are much less common than vector norms, but the C++
133+
`std::norm` operation implements a field norm. To get the (Euclidean)
134+
vector norm in C++, use `std::abs`.
135+
136+
² There's no subscript-∞ in unicode, so I write the infinity norm
137+
without the usual subscript.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# ``Augmented``
2+
3+
## Overview
4+
5+
Consider multiplying two Doubles. A Double has 53 significand bits, so their
6+
product could be up to 106 bits wide before it is rounded to a Double result.
7+
So up to 53 of those 106 bits will be "lost" in that process:
8+
```swift
9+
let a = 1.0 + .ulpOfOne // 1 + 2⁻⁵²
10+
let b = 1.0 - .ulpOfOne // 1 - 2⁻⁵²
11+
let c = a * b // 1 - 2⁻¹⁰⁴ before rounding, rounds to 1.0
12+
```
13+
Sometimes it is necessary to preserve some or all of those low-order bits;
14+
maybe a subsequent subtraction cancels most of the high-order bits, and so
15+
the low-order part of the product suddenly becomes significant:
16+
```swift
17+
let result = 1 - c // exactly zero, but "should be" 2⁻¹⁰⁴
18+
```
19+
Augmented arithmetic is a building-block that library writers can use to
20+
handle cases like this more carefully.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# ``RealModule``
2+
3+
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
4+
5+
## Overview
6+
7+
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
8+
9+
## Topics
10+
11+
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
12+
13+
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# ``Relaxed``
2+
3+
## Overview
4+
5+
Because of rounding, and the arithmetic rules for infinity and NaN values,
6+
floating-point addition and multiplication are not associative:
7+
```swift
8+
let ε = Double.leastNormalMagnitude
9+
let sumLeft = (-1 + 1) + ε // 0 + ε = ε
10+
let sumRight = -1 + (1 + ε) // -1 + 1 = 0
11+
12+
let = Double.infinity
13+
let productLeft =* ε) * // 0 * ∞ = .nan
14+
let productRight = ε ** ) // ε * ∞ = ∞
15+
```
16+
For some algorithms, the distinction between these results is incidental; for
17+
some others it is critical to their correct function. Because of this,
18+
compilers cannot freely change the order of reductions, which prevents some
19+
important optimizations: extraction of instruction-level parallelism and
20+
vectorization.
21+
22+
If you know that you are in a case where the order of elements being summed
23+
or multiplied is incidental, the Relaxed operations give you a mechanism
24+
to communicate that to the compiler and unlock these optimizations. For
25+
example, consider the following two functions:
26+
```swift
27+
func sum(array: [Float]) -> Float {
28+
array.reduce(0, +)
29+
}
30+
31+
func relaxedSum(array: [Float]) -> Float {
32+
array.reduce(0, Relaxed.sum)
33+
}
34+
```
35+
when called on an array with 1000 elements in a Release build, `relaxedSum`
36+
is about 8x faster than `sum` on Apple M2, with a similar speedup on Intel
37+
processors, without the need for any unsafe code or flags.
38+
39+
### multiplyAdd
40+
41+
In addition to `Relaxed.sum` and `Relaxed.product`, `Relaxed` provides the
42+
``multiplyAdd(_:_:_:)`` operation, which communciates to the compiler that
43+
it is allowed to replace separate multiply and add operations with a single
44+
_fused multiply-add_ instruction if its cost model indicates that it would
45+
be advantageous to do so. When targeting processors that support this
46+
instruction, this may be a significant performance advantage.

0 commit comments

Comments
 (0)