conv
conv converts between numbers and text. Parsing takes a []const u8 (a
string or a literal works) and returns T or conv.ParseError; formatting
writes into a buffer you provide and returns how many bytes it wrote.
import conv from "conv";It never allocates and never touches the OS, so it works in every profile,
t0 kernel code included.
Floats are exact. parse_f64 returns the double nearest to the decimal
text, however many digits it has. fmt_f64 with a negative precision writes
the shortest text that reads back as the very same double, so a value
survives a trip through text bit for bit. parse_f32 and fmt_f32 do the
same for f32:
var buf: [32]u8;
let n: i64 = conv.fmt_f64(buf[..], 0.1 + 0.2, -1);
io.println(buf[0..n]); // 0.30000000000000004
let back: f64 = conv.parse_f64(buf[0..n]) or { return 1; };
// back == 0.1 + 0.2, exactlyErrors
pub enum ParseError : u8 {
Empty = 1: "empty input";
InvalidDigit = 2: "invalid digit";
Overflow = 3: "value out of range";
InvalidFormat = 4: "invalid format";
}| Error | When |
|---|---|
Empty | The text is empty. |
InvalidDigit | An integer contains a byte that isn’t a digit (a space, _, ., a second sign, a letter beyond the radix). |
Overflow | The number doesn’t fit: outside i64/u64, or a float too large for f64 (f32 for parse_f32). |
InvalidFormat | A sign with no digits, a malformed float, or anything but true/false for parse_bool. |
If text has both a bad byte and a value out of range, you get
InvalidDigit.
let n: i64 = conv.parse_i64("12x") or {
io.println(err.desc()); // invalid digit
return 1;
};The parsers accept exactly one number and nothing else: no spaces around
it, no _ separators, no 0x prefix. Trim first if your input may have
spaces.
Parsing
parse_i64(s: []const u8) -> i64 or ParseError
A decimal integer: an optional + or -, then digits.
let a: i64 = conv.parse_i64("-42") or { return 1; }; // -42
let b: i64 = conv.parse_i64("+007") or { return 1; }; // 7
let c: i64 = conv.parse_i64("9223372036854775808") or { // Overflow
return 2;
};parse_u64(s: []const u8) -> u64 or ParseError
A decimal unsigned integer up to 18446744073709551615. A - sign is an
InvalidDigit, even in -0.
let max: u64 = conv.parse_u64("18446744073709551615") or { return 1; };parse_i64_radix(s: []const u8, radix: i64) -> i64 or ParseError
An integer in base 2 to 36, with an optional sign. Digits above 9 are the
letters a to z, in either case. There is no prefix: strip a 0x or 0b
yourself.
let h: i64 = conv.parse_i64_radix("ff", 16) or { return 1; }; // 255
let b: i64 = conv.parse_i64_radix("-101", 2) or { return 1; }; // -5
let z: i64 = conv.parse_i64_radix("Zz", 36) or { return 1; }; // 1295A radix outside 2 to 36 stops the program (trap): it’s a bug in the
caller, not bad input.
parse_f64(s: []const u8) -> f64 or ParseError
A decimal floating-point number:
- an optional sign, digits, optionally a
.followed by more digits, and optionally an exponent:eorE, an optional sign and digits:3,-2.5,1e10,6.02214076E+23; - or
inf,+inf,-inf,nan(lowercase).
There must be a digit on both sides of a .: .5 and 5. are
InvalidFormat. The result is correctly rounded (ties to even), for any
number of digits. Numbers too small for a double become 0 (or -0);
numbers too large are Overflow.
let x: f64 = conv.parse_f64("0.1") or { return 1; };
let y: f64 = conv.parse_f64("9007199254740993") or { return 1; }; // 9007199254740992: the tie rounds to even
let z: f64 = conv.parse_f64("-1e-400") or { return 1; }; // -0parse_f32(s: []const u8) -> f32 or ParseError
The same syntax as parse_f64, rounded straight to the nearest f32 (ties
to even). It doesn’t go through f64, which would round twice and can pick
the wrong neighbour. Numbers above about 3.4028235e38 are Overflow.
let x: f32 = conv.parse_f32("0.1") or { return 1; };
let y: f32 = conv.parse_f32("1e39") or { return 2; }; // Overflowparse_bool(s: []const u8) -> bool or ParseError
Exactly true or false.
let verbose: bool = conv.parse_bool("true") or { return 1; };Formatting
The formatters write into dst from index 0 and return the number of bytes
written; slice the buffer with that length (buf[0..n]). If the output
doesn’t fit in dst the program traps, so size buffers with these
constants:
| Constant | Value | For |
|---|---|---|
FMT_I64_MAX_LEN | 20 | fmt_i64 |
FMT_U64_RADIX_MAX_LEN | 64 | fmt_u64_radix (base 2) |
FMT_F64_SHORTEST_MAX_LEN | 25 | fmt_f64 with precision < 0 |
FMT_F64_FIXED_MAX_LEN | 311 | fmt_f64 with precision p >= 0 needs this + p |
FMT_F32_SHORTEST_MAX_LEN | 22 | fmt_f32 with precision < 0 |
FMT_F32_FIXED_MAX_LEN | 41 | fmt_f32 with precision p >= 0 needs this + p |
fmt_i64(dst: []u8, v: i64) -> i64
Decimal, with - for negative numbers.
var buf: [20]u8; // conv.FMT_I64_MAX_LEN
let n: i64 = conv.fmt_i64(buf[..], -1234);
io.println(buf[0..n]); // -1234fmt_u64_radix(dst: []u8, v: u64, radix: i64) -> i64
Base 2 to 36, digits 0-9 then lowercase a-z, no prefix.
var buf: [64]u8; // conv.FMT_U64_RADIX_MAX_LEN
let n: i64 = conv.fmt_u64_radix(buf[..], 255, 16);
io.println(buf[0..n]); // ff
let m: i64 = conv.fmt_u64_radix(buf[..], 5, 2);
io.println(buf[0..m]); // 101A radix outside 2 to 36 traps.
fmt_f64(dst: []u8, v: f64, precision: i64) -> i64
With precision < 0: the shortest text that parse_f64 reads back as
exactly v. Numbers from 0.000001 up to (not including) 1e21 are written
plainly, without a trailing .0; others in scientific notation:
| Value | Text |
|---|---|
1.0 | 1 |
0.1 | 0.1 |
-2.25 | -2.25 |
1e20 | 100000000000000000000 |
1e21 | 1e21 |
0.000001 | 0.000001 |
0.0000001 | 1e-7 |
5e-324 | 5e-324 |
-0.0 | -0 |
With precision >= 0: exactly that many digits after the point (no point
for 0), rounded from the exact value of v, ties to even. Never scientific.
| Call | Text |
|---|---|
fmt_f64(buf[..], 3.14159, 2) | 3.14 |
fmt_f64(buf[..], 2.5, 0) | 2 (a tie: rounds to even) |
fmt_f64(buf[..], 0.125, 2) | 0.12 (0.125 is exact, a tie) |
fmt_f64(buf[..], 1.005, 2) | 1.00 (the double is slightly below 1.005) |
fmt_f64(buf[..], -0.001, 2) | -0.00 |
Infinities are inf and -inf, and every NaN is nan, at any precision.
var buf: [25]u8; // conv.FMT_F64_SHORTEST_MAX_LEN
let n: i64 = conv.fmt_f64(buf[..], 1.0 / 3.0, -1);
io.println(buf[0..n]); // 0.3333333333333333
var fixed: [316]u8; // conv.FMT_F64_FIXED_MAX_LEN + 5
let m: i64 = conv.fmt_f64(fixed[..], 1.0 / 3.0, 5);
io.println(fixed[0..m]); // 0.33333fmt_f32(dst: []u8, v: f32, precision: i64) -> i64
fmt_f64 for f32. With precision < 0 it writes the shortest text that
parse_f32 reads back as exactly v, which is often shorter than the text
of the same value widened to f64:
let x: f32 = 0.1;
var buf: [22]u8; // conv.FMT_F32_SHORTEST_MAX_LEN
let n: i64 = conv.fmt_f32(buf[..], x, -1);
io.println(buf[0..n]); // 0.1
let m: i64 = conv.fmt_f64(buf[..], x as f64, -1);
io.println(buf[0..m]); // 0.10000000149011612With precision >= 0 it writes the same text as fmt_f64 of the widened
value. String interpolation ("{x}") formats an f64 with fmt_f64 and an
f32 with fmt_f32, both with precision -1.
Last modified: