Skip to content

Instantly share code, notes, and snippets.

@travisstaloch
Created February 5, 2026 21:44
Show Gist options
  • Select an option

  • Save travisstaloch/2ec69830c6dbd64e2b73f745f5b8e099 to your computer and use it in GitHub Desktop.

Select an option

Save travisstaloch/2ec69830c6dbd64e2b73f745f5b8e099 to your computer and use it in GitHub Desktop.
human readable number formatter
/// format n as human readable text
const HumanReadable = @This();
n: usize,
pub fn init(n: usize) HumanReadable {
return .{ .n = n };
}
pub const Fmt = std.fmt.Formatter(HumanReadable, format);
const teens: []const [*:0]const u8 = &.{ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eightteen", "nineteen" };
const tens: []const [*:0]const u8 = &.{ "", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" };
const illions: []const [*:0]const u8 = &.{ "", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion" };
/// uses an internal stack to avoid recursion
pub fn format(hr: HumanReadable, w: *std.Io.Writer) std.Io.Writer.Error!void {
var stack: ?usize = hr.n;
var stack2: [3][*:0]const u8 = .{ "", "", "" };
while (stack) |n| {
stack = null;
for (stack2) |s| try writeZ(w, s);
stack2 = .{ "", "", "" };
n: switch (n) {
else => |n1| { // 1000...
// write most significant 3 digits next, push rest to stack
const l: u6 = @intCast(std.math.log10(n1) / 3);
const p = std.math.pow(usize, 10, l * 3);
const n2 = n1 / p;
const n3 = n1 % p;
if (n3 != 0) {
stack = n3;
stack2 = .{
" ",
illions[l],
if (n3 > 99) ", " else " ",
};
}
continue :n n2;
},
100...999 => |n1| {
try writeZ(w, teens[n1 / 100]);
try w.writeAll(" hundred");
const n2 = n1 % 100;
if (n2 != 0) {
try w.writeAll(" ");
continue :n n2;
}
},
20...99 => |n1| {
const n2 = n1 / 10;
try writeZ(w, tens[n2]);
if (n1 % 10 != 0) {
try w.writeAll(" ");
continue :n n1 % 10;
}
},
0...19 => |n1| try writeZ(w, teens[n1]),
}
}
}
fn writeZ(w: *std.Io.Writer, s: [*:0]const u8) !void {
var p = s;
while (p[0] != 0) : (p += 1) {
try w.writeByte(p[0]);
}
}
fn expect(x: anytype, expected: []const u8) !void {
var buf: [256]u8 = undefined;
var w = std.Io.Writer.fixed(&buf);
try w.print("{f}", .{x});
try std.testing.expectEqualStrings(expected, w.buffered());
}
test {
try expect(init(0), "zero");
try expect(init(19), "nineteen");
try expect(init(100), "one hundred");
try expect(init(142), "one hundred forty two");
try expect(init(200), "two hundred");
try expect(init(230), "two hundred thirty");
try expect(init(1042), "one thousand forty two");
try expect(init(911_142), "nine hundred eleven thousand, one hundred forty two");
try expect(init(54535), "fifty four thousand, five hundred thirty five");
try expect(init(10_100_010_101), "ten billion, one hundred million, ten thousand, one hundred one");
try expect(init(49230), "forty nine thousand, two hundred thirty");
}
// test "random" {
// var prng = std.Random.DefaultPrng.init(0);
// for (0..100) |_| {
// const i = prng.random().int(u32);
// std.debug.print("{: >10}: {f}\n", .{ i, HumanReadable3{ .n = i } });
// }
// }
const V2 = struct {
n: usize,
pub fn format(hr: V2, w: *std.Io.Writer) std.Io.Writer.Error!void {
n: switch (hr.n) {
else => |n| {
const l: u6 = @intCast(std.math.log10(n) / 3);
const p = std.math.pow(usize, 10, l * 3);
const n2 = n / p;
try (V2{ .n = n2 }).format(w); // FIXME: eliminate recursion
const mags: []const []const u8 = &.{ "", "K", "M", "B", "T", "Q", "U", "S" };
try w.writeAll(mags[l]);
const n3 = n - n2 * p;
if (n3 != 0) {
try w.writeAll(", ");
continue :n n3;
}
},
0...999 => |n| {
try w.print("{}", .{n});
},
}
}
};
test V2 {
try expect(V2{ .n = 0 }, "0");
try expect(V2{ .n = 19 }, "19");
try expect(V2{ .n = 100 }, "100");
try expect(V2{ .n = 142 }, "142");
try expect(V2{ .n = 200 }, "200");
try expect(V2{ .n = 230 }, "230");
try expect(V2{ .n = 1042 }, "1K, 42");
try expect(V2{ .n = 911_142 }, "911K, 142");
try expect(V2{ .n = 54535 }, "54K, 535");
try expect(V2{ .n = 10_100_010_101 }, "10B, 100M, 10K, 101");
try expect(V2{ .n = 49230 }, "49K, 230");
}
const std = @import("std");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment