Created
February 5, 2026 21:44
-
-
Save travisstaloch/2ec69830c6dbd64e2b73f745f5b8e099 to your computer and use it in GitHub Desktop.
human readable number formatter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// 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