feat(lang): float numeric-limit accessors — examples, unit tests, docs [NL.2]
Finish NL.2 on top of the WIP compiler impl (2e9e4fe): f32/f64 expose
.min/.max plus the float-only .epsilon/.min_positive/.true_min/.inf/.nan,
folded via the shared lowerNumericLimit intercept + builder.constFloat.
- examples/0159: pins every f32/f64 accessor by untagged-union bit
reinterpret against exact IEEE-754 hex (true_min read before any
arithmetic — FTZ/DAZ), plus the defining-property checks
((1+eps)!=1 / (1+eps/2)==1, inf>max, min==-max, true_min<min_positive,
true_min>0, nan!=nan).
- examples/0160: float-only accessor on an int (s32.epsilon/u8.inf/
s64.true_min) and any accessor on a non-numeric type compile-error
cleanly (exit 1, pinned stderr).
- type_resolver.test.zig: floatLimitFor bit-pattern + property tests for
f32/f64, isLimitField coverage, null for non-float/non-limit fields.
- specs.md Numeric Limits: float accessors + the min=-max / min_positive=
smallest-normal / epsilon=ULP-of-1.0 / true_min=smallest-subnormal
clarifications, with the mandatory FTZ/DAZ flush-to-zero caveat.
readme.md overview updated.
This commit is contained in:
58
specs.md
58
specs.md
@@ -283,6 +283,64 @@ n := u64.max; // 18446744073709551615 (all-ones)
|
||||
`string`, a pointer, a `struct`, `void`, an `enum`) is a compile error, never
|
||||
a silent value.
|
||||
|
||||
The **float** types `f32` and `f64` expose the same `.min` / `.max` plus a set of
|
||||
float-only accessors. Each folds, at compile time, to a constant of the queried
|
||||
float type (the same `lowerNumericLimit` intercept, via `builder.constFloat`):
|
||||
|
||||
```sx
|
||||
hi := f64.max; // largest finite double
|
||||
lo := f64.min; // most-NEGATIVE finite = -max (NOT C's DBL_MIN)
|
||||
eps := f64.epsilon; // ULP of 1.0 (f64 = 2^-52, f32 = 2^-23)
|
||||
mp := f64.min_positive; // smallest positive NORMAL (= C DBL_MIN / Rust MIN_POSITIVE)
|
||||
tm := f64.true_min; // smallest positive SUBNORMAL (next value above 0.0)
|
||||
pin := f64.inf; // +infinity
|
||||
qn := f64.nan; // a quiet NaN
|
||||
```
|
||||
|
||||
- **Receiver.** `f32` or `f64`.
|
||||
- **Shared with integers.** `.min` / `.max` are valid on BOTH integer and float
|
||||
types. `.min` is the most-NEGATIVE finite value, i.e. `-max` — consistent with
|
||||
the integer `.min`, and deliberately **NOT** C's `DBL_MIN`/`FLT_MIN` (which is
|
||||
the smallest positive normal; that is `.min_positive` here).
|
||||
- **Float-only accessors.**
|
||||
- `.epsilon` — the ULP of `1.0`: the gap between `1.0` and the next
|
||||
representable value (`f64 = 2^-52 ≈ 2.22e-16`, `f32 = 2^-23`). This is the
|
||||
**machine epsilon** used for relative-tolerance comparisons, **NOT** C#'s
|
||||
`Double.Epsilon` (which is the smallest denormal — that is `.true_min` here).
|
||||
Defining property: `1.0 + epsilon != 1.0` while `1.0 + epsilon/2.0 == 1.0`.
|
||||
- `.min_positive` — the smallest positive **NORMAL** value (`f64 = 2^-1022`,
|
||||
`f32 = 2^-126`). Equals C's `DBL_MIN` / Rust's `MIN_POSITIVE`.
|
||||
- `.true_min` — the smallest positive **SUBNORMAL**: the next value above `0.0`
|
||||
(`f64` bits `0x0000000000000001 = 2^-1074`, `f32` bits `0x00000001 = 2^-149`).
|
||||
Named `true_min` (after Zig's `floatTrueMin`) to avoid the Java/Go/JS
|
||||
`MIN_VALUE` footgun, where a bare `MIN_VALUE` names the smallest *subnormal*
|
||||
yet reads like the most-negative value.
|
||||
- **FTZ/DAZ caveat.** Subnormals are exactly the values that vanish under
|
||||
flush-to-zero (FTZ) / denormals-are-zero (DAZ) CPU modes. If such a mode is
|
||||
active, a loaded `.true_min` can flush to `0.0` on the **first arithmetic
|
||||
operation** that touches it. The folded constant always carries the exact
|
||||
subnormal bit pattern; read or store it through a bit reinterpret *before*
|
||||
any arithmetic if you need the true value to survive. Numerical-library
|
||||
authors who toggle FTZ/DAZ should not be surprised when `true_min * 1.0`
|
||||
reads back as `0.0`.
|
||||
- `.inf` — positive infinity (`inf > max`).
|
||||
- `.nan` — a quiet NaN. The exact mantissa bits are not pinned; the only
|
||||
guaranteed property is that it is unequal to everything, itself included
|
||||
(`nan != nan` is `true` — native float `!=` lowers unordered, issue 0091).
|
||||
- **Float-only on an integer is an error.** `.epsilon` / `.min_positive` /
|
||||
`.true_min` / `.inf` / `.nan` applied to an integer type (`s32.epsilon`,
|
||||
`u8.inf`, `s64.true_min`) is a clean compile error — integer types expose only
|
||||
`.min` / `.max`.
|
||||
- **Pinning the values.** The lexer has no exponent notation and the default
|
||||
float formatter is crude (issue 0090), so float limits can be asserted neither
|
||||
by literal comparison nor by printing. Reinterpret the bits through an untagged
|
||||
union (`union { f: f64; bits: u64 }`) and compare against the exact IEEE-754
|
||||
pattern — `f64.max = 0x7FEFFFFFFFFFFFFF`, `min = 0xFFEFFFFFFFFFFFFF`,
|
||||
`epsilon = 0x3CB0000000000000`, `min_positive = 0x0010000000000000`,
|
||||
`true_min = 0x0000000000000001`, `inf = 0x7FF0000000000000`; the `f32` set is
|
||||
`0x7F7FFFFF` / `0xFF7FFFFF` / `0x34000000` / `0x00800000` / `0x00000001` /
|
||||
`0x7F800000`.
|
||||
|
||||
### Enum Types
|
||||
User-defined sum types with named variants. Variants may optionally carry typed data (tagged unions). Internally, payload-less enums are represented as `i64` (variant index). Enums with payloads are represented as `{ i64, [max_payload_size x i8] }` (tag + data).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user