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:
89
examples/0159-types-float-numeric-limits.sx
Normal file
89
examples/0159-types-float-numeric-limits.sx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// 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 uses a `u64` view so the all-ones-ish positive patterns read as
|
||||||
|
// their true magnitude; the negative `f64.min` pattern (0xFFEF…) overflows the
|
||||||
|
// i64 literal parser, so it is pinned by the `min == -max` property instead.
|
||||||
|
Uf64 :: union { f: f64; bits: u64; }
|
||||||
|
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
|
||||||
|
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_min<min_pos {}\n", f64.true_min < f64.min_positive); // true
|
||||||
|
print("true_min>0 {}\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;
|
||||||
|
}
|
||||||
27
examples/0160-types-float-numeric-limits-errors.sx
Normal file
27
examples/0160-types-float-numeric-limits-errors.sx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Cross-type rules for the numeric-limit accessors. `.min` / `.max` are valid on
|
||||||
|
// BOTH integer and float types, but `.epsilon` / `.min_positive` / `.true_min` /
|
||||||
|
// `.inf` / `.nan` are FLOAT-ONLY. Applying a float-only accessor to an INTEGER
|
||||||
|
// type, or ANY accessor to a non-numeric type, is a clean compile error — never
|
||||||
|
// a silent value, never the `.unresolved` sentinel reaching codegen.
|
||||||
|
//
|
||||||
|
// - float-only accessor on an integer (`s32.epsilon`, `u8.inf`,
|
||||||
|
// `s64.true_min`) → a dedicated "applies only to float types" diagnostic
|
||||||
|
// from the accessor intercept, located at the access;
|
||||||
|
// - any accessor on a non-numeric builtin (`bool.nan`, `string.max`) → the
|
||||||
|
// "numeric limits apply only to integer and float types" diagnostic;
|
||||||
|
// - a user struct (`MyStruct.epsilon`) → the type name is not a builtin, so the
|
||||||
|
// intercept stays out and the existing field-not-found path reports it.
|
||||||
|
// Each case is accurate and located at the access; the program exits non-zero.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
MyStruct :: struct { a: s64; }
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
a := s32.epsilon;
|
||||||
|
b := u8.inf;
|
||||||
|
c := s64.true_min;
|
||||||
|
d := bool.nan;
|
||||||
|
e := string.max;
|
||||||
|
f := MyStruct.epsilon;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1
examples/expected/0159-types-float-numeric-limits.exit
Normal file
1
examples/expected/0159-types-float-numeric-limits.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0159-types-float-numeric-limits.stderr
Normal file
1
examples/expected/0159-types-float-numeric-limits.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
19
examples/expected/0159-types-float-numeric-limits.stdout
Normal file
19
examples/expected/0159-types-float-numeric-limits.stdout
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
f64.true_min true
|
||||||
|
f64.max true
|
||||||
|
f64.epsilon true
|
||||||
|
f64.min_positive true
|
||||||
|
f64.inf true
|
||||||
|
f32.true_min true
|
||||||
|
f32.max true
|
||||||
|
f32.min true
|
||||||
|
f32.epsilon true
|
||||||
|
f32.min_positive true
|
||||||
|
f32.inf true
|
||||||
|
(1+eps)!=1 true
|
||||||
|
(1+eps/2)==1 true
|
||||||
|
inf>max true
|
||||||
|
min==-max true
|
||||||
|
true_min<min_pos true
|
||||||
|
true_min>0 true
|
||||||
|
nan!=nan true
|
||||||
|
typed eps bits true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
error: type 's32' has no '.epsilon' — '.epsilon' applies only to float types (f32/f64); integer types expose only '.min'/'.max'
|
||||||
|
--> examples/0160-types-float-numeric-limits-errors.sx:20:10
|
||||||
|
|
|
||||||
|
20 | a := s32.epsilon;
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: type 'u8' has no '.inf' — '.inf' applies only to float types (f32/f64); integer types expose only '.min'/'.max'
|
||||||
|
--> examples/0160-types-float-numeric-limits-errors.sx:21:10
|
||||||
|
|
|
||||||
|
21 | b := u8.inf;
|
||||||
|
| ^^^^^^
|
||||||
|
|
||||||
|
error: type 's64' has no '.true_min' — '.true_min' applies only to float types (f32/f64); integer types expose only '.min'/'.max'
|
||||||
|
--> examples/0160-types-float-numeric-limits-errors.sx:22:10
|
||||||
|
|
|
||||||
|
22 | c := s64.true_min;
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: type 'bool' has no '.nan' — numeric limits apply only to integer and float types
|
||||||
|
--> examples/0160-types-float-numeric-limits-errors.sx:23:10
|
||||||
|
|
|
||||||
|
23 | d := bool.nan;
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
error: type 'string' has no '.max' — numeric limits apply only to integer and float types
|
||||||
|
--> examples/0160-types-float-numeric-limits-errors.sx:24:10
|
||||||
|
|
|
||||||
|
24 | e := string.max;
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
error: field 'epsilon' not found on type 'Any'
|
||||||
|
--> examples/0160-types-float-numeric-limits-errors.sx:25:10
|
||||||
|
|
|
||||||
|
25 | f := MyStruct.epsilon;
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -90,7 +90,13 @@ Options:
|
|||||||
a compile-time constant of that type: `s64.max` → `9223372036854775807`,
|
a compile-time constant of that type: `s64.max` → `9223372036854775807`,
|
||||||
`u8.min` → `0`, `s3.max` → `3`. It works for every width `s1`..`s64` / `u1`..`u64`
|
`u8.min` → `0`, `s3.max` → `3`. It works for every width `s1`..`s64` / `u1`..`u64`
|
||||||
plus `usize`/`isize`, and is usable anywhere a constant of that type is — including
|
plus `usize`/`isize`, and is usable anywhere a constant of that type is — including
|
||||||
array dimensions (`[u8.max]T` is a 255-element array).
|
array dimensions (`[u8.max]T` is a 255-element array). The float types `f32`/`f64`
|
||||||
|
expose `.min` / `.max` too (with `.min` = most-negative finite = `-max`, **not**
|
||||||
|
C's `DBL_MIN`) plus the float-only `.epsilon` (ULP of 1.0, not C#'s denormal
|
||||||
|
`Epsilon`), `.min_positive` (smallest normal = C `DBL_MIN`), `.true_min` (smallest
|
||||||
|
subnormal — beware flush-to-zero CPU modes), `.inf`, and `.nan`. A float-only
|
||||||
|
accessor on an integer (`s32.epsilon`), or any accessor on a non-numeric type, is
|
||||||
|
a clean compile error. See `specs.md` → Numeric Limits.
|
||||||
|
|
||||||
### Declarations
|
### Declarations
|
||||||
|
|
||||||
|
|||||||
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
|
`string`, a pointer, a `struct`, `void`, an `enum`) is a compile error, never
|
||||||
a silent value.
|
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
|
### 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).
|
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).
|
||||||
|
|
||||||
|
|||||||
@@ -246,3 +246,74 @@ test "TypeResolver.integerLimitFor: null for non-integer receivers and non-limit
|
|||||||
try std.testing.expect(TypeResolver.integerLimitFor("s64", "len") == null);
|
try std.testing.expect(TypeResolver.integerLimitFor("s64", "len") == null);
|
||||||
try std.testing.expect(TypeResolver.integerLimitFor("u8", "epsilon") == null);
|
try std.testing.expect(TypeResolver.integerLimitFor("u8", "epsilon") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "TypeResolver.isLimitField: the accessor set, nothing else" {
|
||||||
|
// The full numeric-limit surface — int .min/.max plus the float-only ones.
|
||||||
|
for ([_][]const u8{ "min", "max", "epsilon", "min_positive", "true_min", "inf", "nan" }) |f| {
|
||||||
|
try std.testing.expect(TypeResolver.isLimitField(f));
|
||||||
|
}
|
||||||
|
// Ordinary fields / near-misses are not limit accessors.
|
||||||
|
try std.testing.expect(!TypeResolver.isLimitField("len"));
|
||||||
|
try std.testing.expect(!TypeResolver.isLimitField("ptr"));
|
||||||
|
try std.testing.expect(!TypeResolver.isLimitField("maximum"));
|
||||||
|
try std.testing.expect(!TypeResolver.isLimitField("Min"));
|
||||||
|
try std.testing.expect(!TypeResolver.isLimitField(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "TypeResolver.floatLimitFor: pinned f64 bit patterns" {
|
||||||
|
const L = struct {
|
||||||
|
fn bits(name: []const u8, field: []const u8) u64 {
|
||||||
|
return @bitCast(TypeResolver.floatLimitFor(name, field).?);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Exact IEEE-754 double bit patterns (the same values the example pins via
|
||||||
|
// a runtime bit reinterpret).
|
||||||
|
try std.testing.expectEqual(@as(u64, 0x7FEFFFFFFFFFFFFF), L.bits("f64", "max"));
|
||||||
|
try std.testing.expectEqual(@as(u64, 0xFFEFFFFFFFFFFFFF), L.bits("f64", "min")); // -max
|
||||||
|
try std.testing.expectEqual(@as(u64, 0x3CB0000000000000), L.bits("f64", "epsilon"));
|
||||||
|
try std.testing.expectEqual(@as(u64, 0x0010000000000000), L.bits("f64", "min_positive"));
|
||||||
|
try std.testing.expectEqual(@as(u64, 0x0000000000000001), L.bits("f64", "true_min"));
|
||||||
|
try std.testing.expectEqual(@as(u64, 0x7FF0000000000000), L.bits("f64", "inf"));
|
||||||
|
// .min = -max (NOT C's DBL_MIN, which is min_positive); the ordering holds.
|
||||||
|
const v = struct {
|
||||||
|
fn f(name: []const u8, field: []const u8) f64 {
|
||||||
|
return TypeResolver.floatLimitFor(name, field).?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try std.testing.expectEqual(v.f("f64", "min"), -v.f("f64", "max"));
|
||||||
|
try std.testing.expect(v.f("f64", "true_min") < v.f("f64", "min_positive"));
|
||||||
|
try std.testing.expect(v.f("f64", "true_min") > 0.0);
|
||||||
|
try std.testing.expect(std.math.isInf(v.f("f64", "inf")));
|
||||||
|
// Quiet NaN: unequal to itself; exact mantissa bits intentionally not pinned.
|
||||||
|
try std.testing.expect(std.math.isNan(v.f("f64", "nan")));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "TypeResolver.floatLimitFor: pinned f32 bit patterns (widened value narrows losslessly)" {
|
||||||
|
const L = struct {
|
||||||
|
// floatLimitFor widens every f32 limit to f64; narrowing back is lossless
|
||||||
|
// (the codegen path does the same via constFloat at the queried width).
|
||||||
|
fn bits(name: []const u8, field: []const u8) u32 {
|
||||||
|
return @bitCast(@as(f32, @floatCast(TypeResolver.floatLimitFor(name, field).?)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try std.testing.expectEqual(@as(u32, 0x7F7FFFFF), L.bits("f32", "max"));
|
||||||
|
try std.testing.expectEqual(@as(u32, 0xFF7FFFFF), L.bits("f32", "min")); // -max
|
||||||
|
try std.testing.expectEqual(@as(u32, 0x34000000), L.bits("f32", "epsilon"));
|
||||||
|
try std.testing.expectEqual(@as(u32, 0x00800000), L.bits("f32", "min_positive"));
|
||||||
|
try std.testing.expectEqual(@as(u32, 0x00000001), L.bits("f32", "true_min"));
|
||||||
|
try std.testing.expectEqual(@as(u32, 0x7F800000), L.bits("f32", "inf"));
|
||||||
|
const nan_v: f32 = @floatCast(TypeResolver.floatLimitFor("f32", "nan").?);
|
||||||
|
try std.testing.expect(std.math.isNan(nan_v));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "TypeResolver.floatLimitFor: null for non-float receivers and non-limit fields" {
|
||||||
|
// Integer / non-numeric / user names are not float-limit folds.
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("s32", "epsilon") == null);
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("u64", "max") == null);
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("usize", "min") == null);
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("bool", "nan") == null);
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("MyStruct", "inf") == null);
|
||||||
|
// A builtin float with a non-limit field is not a fold here.
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("f64", "len") == null);
|
||||||
|
try std.testing.expect(TypeResolver.floatLimitFor("f32", "ptr") == null);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user