diff --git a/lib/strconv/decimal.ww b/lib/strconv/decimal.ww index 38c947ef..78de7c89 100644 --- a/lib/strconv/decimal.ww +++ b/lib/strconv/decimal.ww @@ -48,8 +48,6 @@ package strconv; -import os; - // ref/hare/strconv/decimal.ha:5. def maxshift: u8 = 60u8; @@ -59,7 +57,7 @@ def decimal_point_range: u16 = 2047u16; // ref/hare/strconv/decimal.ha:8-26. Field layout 1:1. The 800-digit // bound covers subnormal doubles (min exp -1074, max mantissa 4e16 // → at most 767 digits; 800 leaves headroom). -export type decimal = struct { +type decimal = struct { digits: [800]u8, nd: size, dp: i32, diff --git a/lib/strconv/ftos.ww b/lib/strconv/ftos.ww index 26a64404..2146942b 100644 --- a/lib/strconv/ftos.ww +++ b/lib/strconv/ftos.ww @@ -54,7 +54,6 @@ package strconv; import math; -import os; // ref/hare/strconv/ftos_ryu.ha:33. (hi:lo) >> s, low 64 bits. Hare's // "TODO: use 128-bit integers" — ww has no u128; pure-u64 decomposition. diff --git a/lib/strconv/ftos_test.ww b/lib/strconv/ftos_test.ww index ae85bf8e..b43e68d8 100644 --- a/lib/strconv/ftos_test.ww +++ b/lib/strconv/ftos_test.ww @@ -14,7 +14,6 @@ package strconv_test; import strconv; -import os; import math; @@ -43,6 +42,15 @@ fn rt64(x: f64) bool = { return false; }; +fn rt32(x: f32) bool = { + match (stof32(f32tos(x), base.DEC)) { + case let v: f32 => { return math.f32bits(v) == math.f32bits(x); }; + case let e: invalid => { return false; }; + case let e: overflow => { return false; }; + }; + return false; +}; + // ftos_test.ha:57-64 (G/void/NONE-equivalent) — fixed-point renders. @test fn f64tos_fixed() void = { assert(!(!chk(13.37, "13.37"))); @@ -136,6 +144,16 @@ fn chkf32(n: f32, want: str) bool = { assert(!(!chkf32(maxnorm, "3.4028235e38"))); }; +@test fn f32_roundtrip_boundaries() void = { + assert(!(!rt32(math.f32frombits(0x00000001u32)))); + assert(!(!rt32(math.f32frombits(0x007FFFFFu32)))); + assert(!(!rt32(math.f32frombits(0x00800000u32)))); + assert(!(!rt32(math.f32frombits(0x00800001u32)))); + assert(!(!rt32(math.f32frombits(0x7F7FFFFFu32)))); + assert(!(!rt32(math.f32frombits(0x807FFFFFu32)))); + assert(!(!rt32(math.f32frombits(0x80800000u32)))); +}; + // ftos_test.ha:11/15/26 — zero, ±infinity, nan (f32 bit patterns). @test fn f32tos_special() void = { assert(!(!chkf32(0.0f32, "0"))); @@ -162,6 +180,10 @@ fn chkf32(n: f32, want: str) bool = { assert(!(!rt64(90071992547409915.0))); // 17-digit halfway pair assert(!(!rt64(90071992547409925.0))); assert(!(!rt64(math.f64frombits(0x0000000000000001u64)))); // F64_MIN_SUBNORMAL (5e-324) + assert(!(!rt64(math.f64frombits(0x000FFFFFFFFFFFFFu64)))); // F64_MAX_SUBNORMAL assert(!(!rt64(math.f64frombits(0x0010000000000000u64)))); // F64_MIN_NORMAL + assert(!(!rt64(math.f64frombits(0x0010000000000001u64)))); // next normal + assert(!(!rt64(math.f64frombits(0x800FFFFFFFFFFFFFu64)))); // negative max subnormal + assert(!(!rt64(math.f64frombits(0x8010000000000000u64)))); // negative min normal assert(!(!rt64(math.f64frombits(0x7FEFFFFFFFFFFFFFu64)))); // F64_MAX_NORMAL }; diff --git a/lib/strconv/int_test.ww b/lib/strconv/int_test.ww index f0766843..4ef9000c 100644 --- a/lib/strconv/int_test.ww +++ b/lib/strconv/int_test.ww @@ -129,6 +129,9 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = { cki_ovf(7, "9223372036854775808", base.DEC); cki_ovf(8, "-9223372036854775809", base.DEC); + // The last multiply wraps to a value larger than its prefix, so a + // post-arithmetic `n < old` check misses this overflow. + cki_ovf(19, "21000000000000000000", base.DEC); cki(9, "0", base.DEC, 0); cki(10, "1", base.DEC, 1); @@ -168,6 +171,7 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = { cku_ovf(35, "18446744073709551616", base.DEC); cku_ovf(36, "184467440737095516150", base.DEC); cku_ovf(37, "-1", base.DEC); + cku_ovf(46, "21000000000000000000", base.DEC); cku(38, "0", base.DEC, 0u64); cku(39, "1", base.DEC, 1u64); @@ -207,6 +211,87 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = { ck_uint(62, "110101", base.BIN, 53u64: uint); // 0b110101 }; +@test fn test_narrow_parse_boundaries() void = { + match (stoi16("32767", base.DEC)) { + case let v: i16 => assert(v == 32767i16); + case let e: invalid => abort(); + case let e: overflow => abort(); + }; + match (stoi16("-32768", base.DEC)) { + case let v: i16 => assert(v == -32768i16); + case let e: invalid => abort(); + case let e: overflow => abort(); + }; + match (stoi16("32768", base.DEC)) { + case let v: i16 => abort(); + case let e: invalid => abort(); + case let e: overflow => { }; + }; + match (stoi16("-32769", base.DEC)) { + case let v: i16 => abort(); + case let e: invalid => abort(); + case let e: overflow => { }; + }; + + match (stoi8("127", base.DEC)) { + case let v: i8 => assert(v == 127i8); + case let e: invalid => abort(); + case let e: overflow => abort(); + }; + match (stoi8("-128", base.DEC)) { + case let v: i8 => assert(v == -128i8); + case let e: invalid => abort(); + case let e: overflow => abort(); + }; + match (stoi8("128", base.DEC)) { + case let v: i8 => abort(); + case let e: invalid => abort(); + case let e: overflow => { }; + }; + match (stoi8("-129", base.DEC)) { + case let v: i8 => abort(); + case let e: invalid => abort(); + case let e: overflow => { }; + }; + + match (stou16("65535", base.DEC)) { + case let v: u16 => assert(v == 65535u16); + case let e: invalid => abort(); + case let e: overflow => abort(); + }; + match (stou16("65536", base.DEC)) { + case let v: u16 => abort(); + case let e: invalid => abort(); + case let e: overflow => { }; + }; + match (stou8("255", base.DEC)) { + case let v: u8 => assert(v == 255u8); + case let e: invalid => abort(); + case let e: overflow => abort(); + }; + match (stou8("256", base.DEC)) { + case let v: u8 => abort(); + case let e: invalid => abort(); + case let e: overflow => { }; + }; +}; + +@test fn test_strerror_static() void = { + let inv: invalid = 0: invalid; + let ie: error = inv; + let ia: str = strerror(ie); + let ib: str = strerror(ie); + assert(ia.ptr == ib.ptr); + assert(streq(ia, "input is not a valid number")); + + let ov: overflow; + let oe: error = ov; + let oa: str = strerror(oe); + let ob: str = strerror(oe); + assert(oa.ptr == ob.ptr); + assert(streq(oa, "input number doesn't fit target type")); +}; + // Verbatim ports of ref/hare/strconv/utos.ha:74-103 (utos/utos_bases) and // itos.ha:54-87 (itos/itos_bases) — Hare's format tests are flat assert // sequences too. Radix-literal inputs are written in DECIMAL (ww value diff --git a/lib/strconv/stof.ww b/lib/strconv/stof.ww index c8c2d50b..51c8b99f 100644 --- a/lib/strconv/stof.ww +++ b/lib/strconv/stof.ww @@ -53,7 +53,6 @@ package strconv; import ascii; import math; -import os; import strings; // ref/hare/strconv/ftos_ryu.ha:12. 64×64→128 result halves. diff --git a/lib/strconv/strconv.ww b/lib/strconv/strconv.ww index b981b147..ae69774c 100644 --- a/lib/strconv/strconv.ww +++ b/lib/strconv/strconv.ww @@ -8,7 +8,6 @@ package strconv; import ascii; import bytes; -import os; import strings; // invalid — input wasn't a valid number in the requested format. @@ -224,14 +223,12 @@ fn parseint(s: str, b: base) ((bool, u64) | invalid | overflow) = { return i: invalid; }; - let old: u64 = n; - - n = n * nb; - n = n + digit; - - if (n < old) { + // Check before multiplying: a wrapped value is not necessarily + // smaller than the preceding prefix. + if (n > (18446744073709551615u64 - digit) / nb) { return overflow{}; }; + n = n * nb + digit; i += 1; }; @@ -403,13 +400,12 @@ export fn stoz(s: str, b: base) (size | invalid | overflow) = { // both"); ftos.ww's f64tos is the live one. f32tos follows in fold-5b // (task #67, gated on the #143 f32-arg-push cgen fix). -// strerror — convert an strconv error to a user-readable string. -// Returns owned str; release via os.free. Mirrors Hare's -// strconv::strerror. +// strerror — convert a strconv error to a user-readable string. +// The returned string has static storage and must not be freed. export fn strerror(e: error) str = { match (e) { - case let v: invalid => return strings.dup("input is not a valid number"); - case let v: overflow => return strings.dup("input number doesn't fit target type"); + case let v: invalid => return "input is not a valid number"; + case let v: overflow => return "input number doesn't fit target type"; }; - return strings.dup(""); + return ""; };