GuideThe Silo HandbookNumericssource

Source guide/numerics.simd

1# Numerics
2
3Silo takes numbers seriously. There are four distinct numeric
4representations, three arithmetic-variant families for when a plain
5`+` isn't enough, and a full set of bitwise operations. This chapter
6walks through all three layers and the reasoning behind each.
7
8## Four representations, one axis
9
10Silo's numbers split along one axis you already care about even if
11you don't realise it: **exact** versus **inexact**.
12
13| Representation | Shape                          | Exact? |
14|----------------|--------------------------------|--------|
15| `Int`          | Arbitrary-precision integer    | Yes    |
16| `Fraction`     | Exact rational (`p/q`)         | Yes    |
17| `Decimal`      | Unbounded decimal              | Yes    |
18| `Float`        | IEEE 754 binary floating point | **No** |
19
20Three of the four are exact. The one that isn't — `Float` — is the
21familiar IEEE 754 representation with all the usual surprises:
22`0.1 0.2 +` is not exactly `0.3`, rounding modes matter, arithmetic
23isn't associative. Everything else in the number tower avoids those
24pitfalls.
25
26```silo
2742                             # Int
28-7                             # Int
292 100 pow                      # Int — still exact, even at 2^100
303/4                            # Fraction
311/3 1/4 +                      # Fraction — exactly 7/12
323.14                           # Float (F64)
3319.99:D64                      # Decimal (D64)
34```
35
36Two of these types come in a family of widths:
37
38- **Float**: `F16`, `F32`, `F64`, `F128`, `F256`. The default for a
39  bare literal like `3.14` is `F64`.
40- **Decimal**: `D32`, `D64`, `D128`. Written with a `:D64` suffix on
41  a literal: `19.99:D64`.
42
43`Int` and `Fraction` don't have width variants because they're
44arbitrary-precision. Int and Decimal can grow as large as the host
45allows; the host decides whether overflow is "never" or "bounded by
46available memory".
47
48## Contamination: exact meets inexact
49
50When two operands have different representations, the result goes
51to whichever one is less precise:
52
53```silo
541 3.0 +                        # ⌊4.0⌉    Int + Float → Float
551/3 0.5 +                      # ⌊0.833…⌉ Fraction + Float → Float
5642 1.0:D64 +                   # ⌊43.0⌉   Int + Decimal → Decimal
571.0:D64 1.0:F64 +              # ⌊2.0⌉    Decimal + Float → Float
58```
59
60The rule is: **any `Float` in the input produces a `Float` in the
61output**. Exactness is preserved only when every operand is exact.
62This is the contamination rule. It makes it impossible to
63accidentally lose exactness without a `Float` literal appearing
64somewhere visible.
65
66If you want to intermix exact and inexact deliberately, you coerce:
67`my-int (F64 .from)` pulls an `Int` into `F64` explicitly.
68
69## Bounded-integer types
70
71A plain `Int` is unbounded. For fixed-width integers — which most
72low-level code wants — Silo uses the
73:gloss[dependent type](./A1-glossary.simd#dependent-type) machinery from
74[chapter 6](./06-types.simd): an integer type parameterised by its
75range.
76
77```silo
78(Int 0..256)                   # unsigned byte
79(Int -2^63..2^63)              # signed 64-bit
80(Int ..)                       # unbounded signed
81```
82
83Standard aliases are defined for the common widths:
84
85| Unsigned | Signed |
86|----------|--------|
87| `U8`     | `I8`   |
88| `U16`    | `I16`  |
89| `U32`    | `I32`  |
90| `U64`    | `I64`  |
91|          | `I128` |
92
93A literal gets a specific width via a suffix: `42:U8`, `-7:I32`,
94`0xFF:U64`.
95
96Because the range lives in the type, the compiler knows what fits
97and what doesn't. `300:U8` is a compile error — not a wrap-around,
98not a silent truncation, a compile error.
99
100## Arithmetic variants
101
102The plain `+`, `-`, `*`, `/` operators produce results of the same
103type as their inputs. On bounded integer types that means something
104has to happen when the mathematical result doesn't fit. Silo lets
105you choose, by providing four variants of each operator.
106
107### Saturating (default)
108
109`+`, `-`, `*`, `/` on bounded types **saturate**: they clamp at the
110type's min or max rather than overflow:
111
112```silo
113255:U8 1:U8 +                  # ⌊255⌉    clamped at U8 max
1140:U8 1:U8 -                    # ⌊0⌉      clamped at U8 min
11542 1 +                         # ⌊43⌉     unbounded Int, no limit
116```
117
118Saturation is the conservative default: the program keeps running,
119no silent wrap, no panic — you just don't get values outside the
120type's range.
121
122### Checked — `+?` `-?` `*?` `/?`
123
124Returns a `(Result T Str)`:
125
126```silo
127127:I8 1:I8 +?                 # ⌊Err("overflow: 127 + 1 exceeds I8 max")⌉
12842:U8 1:U8 +?                  # ⌊Ok(43:U8)⌉
12910 0 /?                        # ⌊Err("division by zero")⌉
130```
131
132Use checked arithmetic when you want to react to overflow
133programmatically, for example to return a structured error from the
134enclosing word.
135
136### Wrapping — `+!` `-!` `*!`
137
138Modular arithmetic — the two's-complement wrap that low-level code
139sometimes wants explicitly:
140
141```silo
142255:U8 1:U8 +!                 # ⌊0⌉       wraps at U8 max
143127:I8 1:I8 +!                 # ⌊-128⌉    wraps at I8 max
144```
145
146The `!` is pronounced "bang" by tradition and signals "I know what
147I'm doing, please wrap". Nothing silent here — the `!` is the
148opt-in.
149
150### Widening — `+_` `-_` `*_`
151
152Widens the result to the next-larger bounded type so it always
153fits:
154
155```silo
156200:U8 200:U8 +_               # ⌊400:U16⌉   widened
157127:I8 127:I8 +_               # ⌊254:I16⌉   widened
158```
159
160Useful when you're going to feed the result into a further
161calculation that can handle the wider type. The `_` suffix
162indicates "give me the widened result".
163
164### Summary table
165
166| Variant   | Suffix | What happens on overflow                       |
167|-----------|--------|------------------------------------------------|
168| Saturating | (none) | Clamp to min/max                              |
169| Checked   | `?`    | Returns `(Result T Str)` with `Err` on overflow |
170| Wrapping  | `!`    | Two's-complement wrap                          |
171| Widening  | `_`    | Result is the next-wider type                  |
172
173Pick the one that matches what you actually want; don't settle for
174"I hope it doesn't overflow".
175
176## Bitwise operations
177
178Every integer type — bounded or unbounded — implements the bitwise
179traits. Each operation is an ordinary method:
180
181```silo
1820xFF:U8 0x0F:U8 .bit-and       # ⌊0x0F:U8⌉
1830xF0:U8 0x0F:U8 .bit-or        # ⌊0xFF:U8⌉
1840xFF:U8 0x0F:U8 .bit-xor       # ⌊0xF0:U8⌉
1850xF0:U8 .bit-not               # ⌊0x0F:U8⌉
1861:U8 4 .shl                    # ⌊0x10:U8⌉   1 << 4
1870x10:U8 2 .shr                 # ⌊0x04:U8⌉   16 >> 2
188```
189
190The six traits are `BitAnd`, `BitOr`, `BitXor`, `BitNot`, `Shl`,
191`Shr`. A convenience trait `Bitwise` bundles all six, so a generic
192word that needs the whole bitwise vocabulary can depend on a
193single bound:
194
195```silo
196:fn popcount ( a -> Int ) { (Bitwise a) }
197  ...
198:end
199```
200
201Shift amounts accept any unsigned integer. The host handles any
202out-of-range shift according to its platform; you can reason about
203the operation as "shift by this many bits, clamped to the width".
204
205## Key points
206
207- Four representations — `Int`, `Fraction`, `Decimal`, `Float` —
208  with exact/inexact contamination: a `Float` anywhere in the
209  inputs makes the output a `Float`.
210- Bounded integer types are a
211  :gloss[dependent-typed](./A1-glossary.simd#dependent-type) refinement
212  of `Int`. Common widths have aliases (`U8`…`U64`, `I8`…`I128`).
213- Four arithmetic variants cover the "what happens on overflow"
214  question: saturating (default), checked `?`, wrapping `!`,
215  widening `_`. Pick deliberately; the defaults never silently
216  wrap.
217- Bitwise operations are methods on the `BitAnd`/`BitOr`/
218  `BitXor`/`BitNot`/`Shl`/`Shr` traits; the `Bitwise` convenience
219  trait bundles all six.
220
221Next: [pattern matching](./08-pattern-matching.simd), including the
222`Never` type and existential downcasting.