lib/strconv: add f64tos
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -28,6 +28,13 @@ lib/**/*.combined.ww
|
|||||||
examples/**/*.o
|
examples/**/*.o
|
||||||
examples/**/*.s
|
examples/**/*.s
|
||||||
examples/**/*.combined.ww
|
examples/**/*.combined.ww
|
||||||
|
|
||||||
|
# `test/wcc/data/` holds .ww fixtures fed to the C-side wcc tests
|
||||||
|
# (e.g. attest_pass.ww). `ww build` against any of those drops the
|
||||||
|
# usual triplet next to the source — only the .ww is tracked.
|
||||||
|
test/wcc/data/*.o
|
||||||
|
test/wcc/data/*.s
|
||||||
|
test/wcc/data/*.combined.ww
|
||||||
examples/mandelbrot/mandelbrot
|
examples/mandelbrot/mandelbrot
|
||||||
examples/cmatrix/cmatrix
|
examples/cmatrix/cmatrix
|
||||||
examples/lisp/lisp
|
examples/lisp/lisp
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -134,7 +134,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
|
|||||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||||
selfhost/cmd/wcc/cgendecl.ww \
|
selfhost/cmd/wcc/cgendecl.ww \
|
||||||
lib/os/os.ww \
|
lib/os/os.ww lib/strconv/strconv.ww \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
cd $(BIN) && ./ww build \
|
cd $(BIN) && ./ww build \
|
||||||
@@ -181,6 +181,7 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \
|
|||||||
# The driver pulls in lib/os (default search path) and selfhost/cmd/wcc
|
# The driver pulls in lib/os (default search path) and selfhost/cmd/wcc
|
||||||
# (for the bump arena). It then orchestrates w6c/w6a/w6l like the C driver.
|
# (for the bump arena). It then orchestrates w6c/w6a/w6l like the C driver.
|
||||||
$(BIN)/ww_ww: selfhost/cmd/ww/main.ww selfhost/cmd/wcc/mem.ww lib/os/os.ww \
|
$(BIN)/ww_ww: selfhost/cmd/ww/main.ww selfhost/cmd/wcc/mem.ww lib/os/os.ww \
|
||||||
|
lib/strconv/strconv.ww \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
cd $(BIN) && ./ww build \
|
cd $(BIN) && ./ww build \
|
||||||
|
|||||||
@@ -105,3 +105,84 @@ export fn stou64(s: str) (u64 | invalid | overflow) = {
|
|||||||
};
|
};
|
||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// f64tos — write `v` in decimal into `buf` and return the byte count.
|
||||||
|
// Hare name; this is the buffer-in Plan 9 subset of Hare's
|
||||||
|
// `f64tos(n) const str`. Today's surface:
|
||||||
|
//
|
||||||
|
// - finite values only. NaN/±Inf detection needs an f64→u64 bit
|
||||||
|
// reinterpret cast that the cgen doesn't expose yet.
|
||||||
|
// - fixed-point only, up to 6 fractional digits. Trailing zeros
|
||||||
|
// after the decimal point are trimmed. Trailing '.' is dropped.
|
||||||
|
// - magnitudes ≥ 9e18 (overflows i64 in the integer-part cast)
|
||||||
|
// fall back to the literal token "huge". Hare would print these
|
||||||
|
// in scientific notation via Ryū; we will graduate when the
|
||||||
|
// compiler grows the bit-reinterpret cast.
|
||||||
|
//
|
||||||
|
// Round-trip is therefore lossy past 6 fractional digits; callers
|
||||||
|
// that need bit-exact recovery should not use this until the
|
||||||
|
// graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no
|
||||||
|
// decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf,
|
||||||
|
// 0.1)` writes "0.1".
|
||||||
|
//
|
||||||
|
// No float literals in the body — 990's wwdump diff requires this
|
||||||
|
// file's TK_FLOAT count to match between C and ww front-ends, and
|
||||||
|
// the ww-side wwdump currently skips TK_FLOAT.fval while the C side
|
||||||
|
// %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses:
|
||||||
|
// build f64 constants via int-to-f64 casts.
|
||||||
|
export fn f64tos(buf: []u8, v: f64) i32 = {
|
||||||
|
let out: i32 = 0;
|
||||||
|
let f: f64 = v;
|
||||||
|
let zero: f64 = 0: f64;
|
||||||
|
if (f < zero) {
|
||||||
|
buf[out] = 45u8; // '-'
|
||||||
|
out += 1;
|
||||||
|
f = -f;
|
||||||
|
};
|
||||||
|
// 9e18 is comfortably under I64_MAX (9.22e18). Past this the
|
||||||
|
// `f: i64` cast wraps and the integer part comes back as garbage.
|
||||||
|
let cap: f64 = 9000000000000000000i64: f64;
|
||||||
|
if (f >= cap) {
|
||||||
|
let s: str = "huge";
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; };
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
let ip: i64 = f: i64;
|
||||||
|
// Fractional part scaled to 6 decimal digits, with round-to-
|
||||||
|
// nearest via +0.5. (f64 compound assigns mis-lower in cgen —
|
||||||
|
// use the explicit form, as the rest of lib does.)
|
||||||
|
let frac: f64 = f - (ip: f64);
|
||||||
|
let scale: f64 = 1000000: f64;
|
||||||
|
frac = frac * scale;
|
||||||
|
let half: f64 = (1: f64) / (2: f64);
|
||||||
|
let fp: i64 = (frac + half): i64;
|
||||||
|
// Carry: e.g. 0.9999996 rounds fp up to 1000000 and the integer
|
||||||
|
// part needs to advance.
|
||||||
|
if (fp >= 1000000) {
|
||||||
|
ip += 1;
|
||||||
|
fp = 0;
|
||||||
|
};
|
||||||
|
let itmp: [32]u8;
|
||||||
|
let in: i32 = i64tos(itmp[0:32], ip);
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; };
|
||||||
|
if (fp == 0) { return out; };
|
||||||
|
buf[out] = 46u8; // '.'
|
||||||
|
out += 1;
|
||||||
|
let ftmp: [16]u8;
|
||||||
|
let m: i32 = u64tos(ftmp[0:16], fp: u64);
|
||||||
|
// Pad fractional to 6 digits with leading zeros (e.g. 0.05 →
|
||||||
|
// fp=50000, m=5, pad one '0' before "50000").
|
||||||
|
let z: i32 = 6 - m;
|
||||||
|
for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; };
|
||||||
|
k = 0;
|
||||||
|
for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; };
|
||||||
|
// Trim trailing zeros in the fractional part (we know fp != 0,
|
||||||
|
// so the loop stops before erasing the dot).
|
||||||
|
for (out > 0) {
|
||||||
|
if (buf[out - 1] != 48u8) { break; };
|
||||||
|
out -= 1;
|
||||||
|
};
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -336,6 +336,87 @@ export fn stou64(s: str) (u64 | invalid | overflow) = {
|
|||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// f64tos — write `v` in decimal into `buf` and return the byte count.
|
||||||
|
// Hare name; this is the buffer-in Plan 9 subset of Hare's
|
||||||
|
// `f64tos(n) const str`. Today's surface:
|
||||||
|
//
|
||||||
|
// - finite values only. NaN/±Inf detection needs an f64→u64 bit
|
||||||
|
// reinterpret cast that the cgen doesn't expose yet.
|
||||||
|
// - fixed-point only, up to 6 fractional digits. Trailing zeros
|
||||||
|
// after the decimal point are trimmed. Trailing '.' is dropped.
|
||||||
|
// - magnitudes ≥ 9e18 (overflows i64 in the integer-part cast)
|
||||||
|
// fall back to the literal token "huge". Hare would print these
|
||||||
|
// in scientific notation via Ryū; we will graduate when the
|
||||||
|
// compiler grows the bit-reinterpret cast.
|
||||||
|
//
|
||||||
|
// Round-trip is therefore lossy past 6 fractional digits; callers
|
||||||
|
// that need bit-exact recovery should not use this until the
|
||||||
|
// graduate-to-Ryū step lands. `f64tos(buf, 1.0)` writes "1" (no
|
||||||
|
// decimal point), `f64tos(buf, 1.5)` writes "1.5", `f64tos(buf,
|
||||||
|
// 0.1)` writes "0.1".
|
||||||
|
//
|
||||||
|
// No float literals in the body — 990's wwdump diff requires this
|
||||||
|
// file's TK_FLOAT count to match between C and ww front-ends, and
|
||||||
|
// the ww-side wwdump currently skips TK_FLOAT.fval while the C side
|
||||||
|
// %g-formats it. Same trick lib/ww/lex/lex.ww's parsef64 uses:
|
||||||
|
// build f64 constants via int-to-f64 casts.
|
||||||
|
export fn f64tos(buf: []u8, v: f64) i32 = {
|
||||||
|
let out: i32 = 0;
|
||||||
|
let f: f64 = v;
|
||||||
|
let zero: f64 = 0: f64;
|
||||||
|
if (f < zero) {
|
||||||
|
buf[out] = 45u8; // '-'
|
||||||
|
out += 1;
|
||||||
|
f = -f;
|
||||||
|
};
|
||||||
|
// 9e18 is comfortably under I64_MAX (9.22e18). Past this the
|
||||||
|
// `f: i64` cast wraps and the integer part comes back as garbage.
|
||||||
|
let cap: f64 = 9000000000000000000i64: f64;
|
||||||
|
if (f >= cap) {
|
||||||
|
let s: str = "huge";
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k < s.len) { buf[out] = s[k]; out += 1; k += 1; };
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
let ip: i64 = f: i64;
|
||||||
|
// Fractional part scaled to 6 decimal digits, with round-to-
|
||||||
|
// nearest via +0.5. (f64 compound assigns mis-lower in cgen —
|
||||||
|
// use the explicit form, as the rest of lib does.)
|
||||||
|
let frac: f64 = f - (ip: f64);
|
||||||
|
let scale: f64 = 1000000: f64;
|
||||||
|
frac = frac * scale;
|
||||||
|
let half: f64 = (1: f64) / (2: f64);
|
||||||
|
let fp: i64 = (frac + half): i64;
|
||||||
|
// Carry: e.g. 0.9999996 rounds fp up to 1000000 and the integer
|
||||||
|
// part needs to advance.
|
||||||
|
if (fp >= 1000000) {
|
||||||
|
ip += 1;
|
||||||
|
fp = 0;
|
||||||
|
};
|
||||||
|
let itmp: [32]u8;
|
||||||
|
let in: i32 = i64tos(itmp[0:32], ip);
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k < in) { buf[out] = itmp[k]; out += 1; k += 1; };
|
||||||
|
if (fp == 0) { return out; };
|
||||||
|
buf[out] = 46u8; // '.'
|
||||||
|
out += 1;
|
||||||
|
let ftmp: [16]u8;
|
||||||
|
let m: i32 = u64tos(ftmp[0:16], fp: u64);
|
||||||
|
// Pad fractional to 6 digits with leading zeros (e.g. 0.05 →
|
||||||
|
// fp=50000, m=5, pad one '0' before "50000").
|
||||||
|
let z: i32 = 6 - m;
|
||||||
|
for (z > 0) { buf[out] = 48u8; out += 1; z -= 1; };
|
||||||
|
k = 0;
|
||||||
|
for (k < m) { buf[out] = ftmp[k]; out += 1; k += 1; };
|
||||||
|
// Trim trailing zeros in the fractional part (we know fp != 0,
|
||||||
|
// so the loop stops before erasing the dot).
|
||||||
|
for (out > 0) {
|
||||||
|
if (buf[out - 1] != 48u8) { break; };
|
||||||
|
out -= 1;
|
||||||
|
};
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
// MODULE: ascii
|
// MODULE: ascii
|
||||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||||
|
|||||||
Reference in New Issue
Block a user