// Float numeric-limit accessors: `f32`/`f64` expose `.min` / `.max` (sibling of // the integer `.min`/`.max`, NL.1) plus the float-only `.epsilon`, // `.min_positive`, `.true_min`, `.inf`, and `.nan`. Each folds, at compile time, // to a constant of the QUERIED float type via the same `lowerNumericLimit` // intercept as the integer case (`builder.constFloat` + the `std.math` // constants), driven by `TypeResolver.floatLimitFor`. // // The lexer has no exponent notation and the default float formatter is crude // (issue 0090), so these limits can be pinned NEITHER by literal comparison NOR // by printing. Every accessor is asserted instead by reinterpreting its bits // through an untagged union and comparing against the exact IEEE-754 hex // pattern — plus the defining-property checks that no other value could satisfy. // // Semantics (Agra-ruled, consistent with the integer accessors): // .min = most-NEGATIVE finite (= -max), NOT C's DBL_MIN // .max = largest finite // .epsilon = ULP of 1.0 (next f after 1.0 minus 1.0), NOT C#'s denormal Epsilon // .min_positive = smallest positive NORMAL (= C DBL_MIN / Rust MIN_POSITIVE) // .true_min = smallest positive SUBNORMAL (next value above 0.0) // .inf = +infinity // .nan = a quiet NaN // // Regression (issue 0091): `f64.nan != f64.nan` is true — native float `!=` // lowers UNORDERED, so a NaN compares unequal to everything including itself. #import "modules/std.sx"; // `bits` mirrors each float's raw IEEE-754 storage. f64 needs 64 bits, f32 32. // The f64 union's `bits` (u64) view reads the all-ones-ish positive patterns as // their true magnitude; its `s` (s64) view pins the negative `f64.min` pattern // (0xFFEF…), whose unsigned form overflows the u64 literal parser, by comparing // the signed reinterpret to -4503599627370497. Uf64 :: union { f: f64; bits: u64; s: s64; } Uf32 :: union { f: f32; bits: u32; } main :: () -> s32 { o : Uf64 = ---; // Read `.true_min` (a subnormal) FIRST and through the union only — never via // arithmetic. Under flush-to-zero / denormals-are-zero CPU modes a subnormal // can flush to 0.0 on the first arithmetic op, so the bit reinterpret is the // only reliable channel for it. o.f = f64.true_min; print("f64.true_min {}\n", o.bits == 0x0000000000000001); // true o.f = f64.max; print("f64.max {}\n", o.bits == 0x7FEFFFFFFFFFFFFF); // true // f64.min = -max; its bit pattern 0xFFEFFFFFFFFFFFFF overflows an unsigned u64 // literal, so it is pinned directly via the SIGNED s64 view: -4503599627370497. o.f = f64.min; print("f64.min {}\n", o.s == -4503599627370497); // true (bits 0xFFEFFFFFFFFFFFFF) o.f = f64.epsilon; print("f64.epsilon {}\n", o.bits == 0x3CB0000000000000); // true o.f = f64.min_positive; print("f64.min_positive {}\n", o.bits == 0x0010000000000000); // true o.f = f64.inf; print("f64.inf {}\n", o.bits == 0x7FF0000000000000); // true p : Uf32 = ---; p.f = f32.true_min; print("f32.true_min {}\n", p.bits == 0x00000001); // true p.f = f32.max; print("f32.max {}\n", p.bits == 0x7F7FFFFF); // true p.f = f32.min; print("f32.min {}\n", p.bits == 0xFF7FFFFF); // true p.f = f32.epsilon; print("f32.epsilon {}\n", p.bits == 0x34000000); // true p.f = f32.min_positive; print("f32.min_positive {}\n", p.bits == 0x00800000); // true p.f = f32.inf; print("f32.inf {}\n", p.bits == 0x7F800000); // true // Defining-property checks — true epsilon is the ULP of 1.0: adding it to 1.0 // changes the value, adding half of it does not (round-to-nearest-even). print("(1+eps)!=1 {}\n", (1.0 + f64.epsilon) != 1.0); // true print("(1+eps/2)==1 {}\n", (1.0 + f64.epsilon/2.0) == 1.0); // true print("inf>max {}\n", f64.inf > f64.max); // true // f64.min = -max (the 0xFFEF… bit pattern overflows the i64 literal parser). print("min==-max {}\n", f64.min == -f64.max); // true print("true_min0 {}\n", f64.true_min > 0.0); // true // Quiet NaN: unequal to everything, itself included (mantissa bits not pinned). print("nan!=nan {}\n", f64.nan != f64.nan); // true // Result carries the QUERIED type: each binding is declared with the float // type and round-trips, so a mistyped fold (boxed as Any / wrong width) would // not type-check here. e64 : f64 = f64.epsilon; e32 : f32 = f32.epsilon; q : Uf64 = ---; q.f = e64; r : Uf32 = ---; r.f = e32; print("typed eps bits {}\n", q.bits == 0x3CB0000000000000 and r.bits == 0x34000000); // true return 0; }