From 69e31355cecba8d8b06a5e3910978736151ce8e0 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 13 Jun 2026 10:33:57 +0900 Subject: [PATCH] lib/fmt: render i64::MIN via math.absi64, not in-i64 negate (#67) i64dec computed the magnitude with `n = -n` inside i64, which wraps at i64::MIN (-MIN == MIN stays negative), so the `for (n > 0)` loop never ran and println(i64::MIN) emitted only the bare '-'. The whole print/ println/fprint family was affected at the single boundary value (and int MIN, since int is 8B). ref/hare/fmt/print.ha:124-129 takes the magnitude through math::absi64 into a u64; do the same (math.absi64 exists, lib/math/math.ww:12). ww's own printf path was already correct ((-v): u64), so print and printf now agree. fmttest gains fprinti64min + fprintintmin pinning the boundary. --- lib/fmt/fmt.ww | 19 +++++++++++-------- lib/fmt/fmttest.ww | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/fmt/fmt.ww b/lib/fmt/fmt.ww index 17329971..1cbdd99f 100644 --- a/lib/fmt/fmt.ww +++ b/lib/fmt/fmt.ww @@ -22,6 +22,7 @@ package fmt; import io; +import math; import memio; import os; import strings; @@ -38,16 +39,18 @@ let i64dec_buf: [21]u8; // of strconv.i64tos for the pre-printf-family print path; the // printf-family path below dispatches through strconv directly. fn i64dec(v: i64) str = { - let neg: bool = false; - let n: i64 = v; - if (n < 0) { neg = true; n = -n; }; + // ref/hare/fmt/print.ha:124-129: magnitude via math::absi64 into u64, + // sign tested separately. `n = -n` on an i64 wraps at i64::MIN + // (-MIN == MIN), so the old loop never ran and only the '-' printed. + let neg: bool = v < 0; + let n: u64 = math.absi64(v); let tmp: [20]u8; let i: i32 = 0; - if (n == 0) { tmp[0] = '0'; i = 1; }; - for (n > 0) { - let d: i64 = n % 10i64; - tmp[i] = (d + 48i64): u8; - n = n / 10i64; + if (n == 0u64) { tmp[0] = '0'; i = 1; }; + for (n > 0u64) { + let d: u64 = n % 10u64; + tmp[i] = (d + 48u64): u8; + n = n / 10u64; i += 1; }; let out: i32 = 0; diff --git a/lib/fmt/fmttest.ww b/lib/fmt/fmttest.ww index 87b813d0..e60f2a66 100644 --- a/lib/fmt/fmttest.ww +++ b/lib/fmt/fmttest.ww @@ -76,6 +76,44 @@ fn errsource() io.stream = { match (c) { case void => {}; case let eioe: io.error => abort(); }; }; +// ---- fprint: i64::MIN / int MIN whole-magnitude (#67) ------------------ +// i64dec used `n = -n` within i64, which wraps at i64::MIN (-MIN == MIN), +// so the digit loop never ran and only the bare '-' was emitted. The fix +// takes the magnitude through math.absi64 into u64. These rows pin the +// boundary value plus a normal negative, zero, and int MIN. + +@test fn fprinti64min() void = { + let mem: memio.stream = memio.dynamic(); + let s: io.stream = &mem.vt; + + let m: i64 = -9223372036854775807i64 - 1i64; + let r: (size | io.error) = fmt.fprint(s, m); + match (r) { + case let n: size => { assert(!(n: i32 != 20)); }; + case let eioe: io.error => abort(); + }; + assert(!(!streq(memio.string(&mem), "-9223372036854775808"))); + + let c: (void | io.error) = io.close(s); + match (c) { case void => {}; case let eioe: io.error => abort(); }; +}; + +@test fn fprintintmin() void = { + let mem: memio.stream = memio.dynamic(); + let s: io.stream = &mem.vt; + + let m: int = -9223372036854775807 - 1; + let r: (size | io.error) = fmt.fprint(s, m, -7i64, 0i64); + match (r) { + case let n: size => { assert(!(n: i32 != 25)); }; // "-9223372036854775808 -7 0" + case let eioe: io.error => abort(); + }; + assert(!(!streq(memio.string(&mem), "-9223372036854775808 -7 0"))); + + let c: (void | io.error) = io.close(s); + match (c) { case void => {}; case let eioe: io.error => abort(); }; +}; + // ---- fprint: bool + rune renders as "true A" -------------------------- @test fn fprintboolrune() void = {