fmt

tape
import fmt from "fmt";

let addr: string = fmt.hex(0xBEEF);               // "beef"
let pair: string = fmt.hex_padded(10, 2);         // "0a"
let price: string = fmt.float_f64(19.999, 2);     // "20.00"
let size: string = fmt.bytes_human(1572864);      // "1.5 MiB"

String interpolation ("{x}") prints integers in decimal and floats in their shortest form. fmt covers the rest: other bases, a fixed number of decimals, and byte counts. For aligning text in columns, use str.pad_left and friends.

Each function comes in two forms. The plain one returns a new string. The _to one writes into a buffer you pass and returns how many bytes it wrote, so you can format many numbers without allocating:

tape
var buf: [16]u8;
let n: i64 = fmt.hex_to(buf[..], 48879);
let text: []const u8 = buf[0..n];               // "beef", no allocation

A _to function stops the program with a trap when the buffer is too small. The *_MAX_LEN constants give a size that is always enough.

fmt is a pure module.

Hexadecimal and binary

FunctionReturns
hex(v: u64) -> stringv in lowercase hex: "ff", "0"
hex_padded(v: u64, digits: i64) -> stringAt least digits hex digits, zeros in front: "00ff"
bin(v: u64) -> stringv in binary: "101"
bin_padded(v: u64, digits: i64) -> stringAt least digits binary digits: "00000101"
hex_to(dst, v), hex_padded_to(dst, v, digits), bin_to(dst, v), bin_padded_to(dst, v, digits)The same, written into dst: []u8; return the length

There’s no 0x prefix and no leading zeros unless you ask for a width. A value that needs more digits than digits is never cut. digits must be between 0 and 64.

To format a signed number, cast it: fmt.hex(x as u64) gives the two’s-complement bits, so -1 is "ffffffffffffffff".

tape
let rgb: u64 = 0x336699;
io.println("#{fmt.hex_padded(rgb, 6)}");    // "#336699"
let flags: u64 = 5;
io.println("flags: {fmt.bin_padded(flags, 8)}");   // "flags: 00000101"
ConstantValue
HEX_MAX_LEN16: enough for hex_to of any value
BIN_MAX_LEN64: enough for bin_to of any value
PADDED_MAX_DIGITS64: the largest digits

Floating point

FunctionReturns
float_f64(v: f64, precision: i64) -> stringv with precision digits after the point
float_f64_to(dst, v: f64, precision: i64) -> i64The same, into dst

With precision 0 or more you get exactly that many digits after the point (and no point for 0), correctly rounded from the exact value of v. With a negative precision you get the shortest text that reads back as v, the same as "{v}".

tape
fmt.float_f64(3.14159, 2);    // "3.14"
fmt.float_f64(2.5, 0);        // "2" (ties round to even)
fmt.float_f64(1.0, 3);        // "1.000"
fmt.float_f64(0.1, -1);       // "0.1"

Rounding is of the stored value: 2.675 is stored as slightly less than 2.675, so fmt.float_f64(2.675, 2) is "2.67". Infinities print as inf and -inf, not-a-number as nan.

float_f64_to needs up to conv.FMT_F64_SHORTEST_MAX_LEN bytes for the shortest form and conv.FMT_F64_FIXED_MAX_LEN + precision otherwise (see conv).

Byte counts

FunctionReturns
bytes_human(n: i64) -> stringn bytes in binary units: "512 B", "1.5 KiB", "3.2 GiB"
bytes_human_to(dst, n: i64) -> i64The same, into dst
BYTES_HUMAN_MAX_LEN16: enough for any i64

Units are powers of 1024: KiB, MiB, GiB, TiB, PiB, EiB. Below 1024 the exact count is shown; from there on one decimal, rounded (halves up). A value that rounds up to 1024 of a unit is shown in the next one, and negative counts get a minus sign.

tape
fmt.bytes_human(1023);        // "1023 B"
fmt.bytes_human(1024);        // "1.0 KiB"
fmt.bytes_human(1048575);     // "1.0 MiB"
fmt.bytes_human(-2048);       // "-2.0 KiB"

Last modified: