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.