Resolves issue 0090. The `{}` integer formatter mis-rendered both ends of
the 64-bit range:
- `int_to_string` computed the magnitude as `0 - n`, which overflows for
`s64::MIN` (its magnitude is unrepresentable as a positive s64) — the
value stayed negative, the digit loop ran zero times, so only `-`
printed. It now extracts digits straight from `n` (per-digit
`|n % 10|`, `n` truncating toward zero), never negating MIN.
- `any_to_string`'s `case int:` formatted every integer as s64, so a u64
all-ones value printed as `-1`. There was no `uint` type-category to
distinguish signedness. Added an additive `type_is_unsigned(T)`
reflection builtin (static fold + dynamic interp/LLVM paths, mirroring
`type_name`), backed by the new `TypeTable.isUnsignedInt` predicate, and
a `uint_to_string` formatter (unsigned decimal via long-division over
four 16-bit limbs). `case int:` routes through `type_is_unsigned(type)`.
The 16-bit-limb split is factored into a shared `decompose_u16x4`, now
reused by `int_to_hex_string` (no second unsigned-math routine).
Regression: examples/0046-basic-int-formatter-extremes pins both extremes
plus a width spread; unit tests cover `isUnsignedInt`. Docs (specs.md
representation note, readme std API) updated for unsigned/extreme `{}`
behavior. IR snapshots refreshed for the two new std functions.
38 lines
1.4 KiB
Plaintext
38 lines
1.4 KiB
Plaintext
// Integer `{}` formatting across the full signed/unsigned range.
|
|
//
|
|
// Regression (issue 0090): the `{}` formatter was s64-based — it negated
|
|
// the value to print the sign (so s64::MIN, whose magnitude is
|
|
// unrepresentable as a positive s64, rendered as a bare "-"), and it had
|
|
// no unsigned-aware path (so a u64 all-ones value printed as the s64
|
|
// reinterpretation, "-1"). Both extremes now render correctly: signed
|
|
// MIN prints all its digits, and unsigned integers print as unsigned
|
|
// decimal across all 64 bits.
|
|
|
|
#import "modules/std.sx";
|
|
|
|
main :: () {
|
|
// Signed extreme: magnitude is never negated, so MIN survives.
|
|
print("s64.min={}\n", s64.min);
|
|
print("s64.max={}\n", s64.max);
|
|
|
|
// Unsigned extreme: all 64 bits as unsigned decimal, not -1.
|
|
print("u64.max={}\n", u64.max);
|
|
|
|
// Spread across widths — signed.
|
|
print("s8.min={} s8.max={}\n", s8.min, s8.max);
|
|
print("s16.min={} s16.max={}\n", s16.min, s16.max);
|
|
print("s32.min={} s32.max={}\n", s32.min, s32.max);
|
|
|
|
// Spread across widths — unsigned (max is all-ones for that width).
|
|
print("u8.max={} u16.max={}\n", u8.max, u16.max);
|
|
print("u32.max={}\n", u32.max);
|
|
|
|
// Mins of unsigned widths and zero.
|
|
print("u8.min={} u64.min={} zero={}\n", u8.min, u64.min, 0);
|
|
|
|
// Ordinary signed/unsigned values still print correctly.
|
|
neg : s32 = -42;
|
|
pos : u32 = 4000000000;
|
|
print("neg={} pos={}\n", neg, pos);
|
|
}
|