#Print digit separators

1 messages · Page 1 of 1 (latest)

spring stone
#

Any recommended strategy for printing digit separators (such as "1,000,000") or other custom int formatting? Looking for a short non-allocating solution. The easy thing for me is to just have a separate fn to write the int. But if plugging into the print formatting is easy, that might be nicer.

wary glen
#

I don't think there's a formatter for it in std, but it should be fairly easy to do yourself

#

Negative ints and generic types left as an exercise for the reader :)

const std = @import("std");

fn formatIntSep(n: u64, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
    // Count digits
    var x = n;
    var exp: u64 = 1;
    var digits: std.math.Log2Int(u64) = 0;
    while (x > 0) {
        x /= 10;
        exp *= 10;
        digits += 1;
    }

    // Print integer
    if (digits == 0) {
        return writer.writeAll("0");
    }
    while (digits > 0) {
        exp /= 10;
        digits -= 1;
        try writer.writeByte('0' + @intCast(u8, n / exp % 10));
        if (digits > 0 and digits % 3 == 0) {
            try writer.writeByte('\'');
        }
    }
}

fn fmtIntSep(n: u64) std.fmt.Formatter(formatIntSep) {
    return .{ .data = n };
}

test "formatIntSep" {
    var buf = std.ArrayList(u8).init(std.testing.allocator);
    defer buf.deinit();
    try buf.writer().print("{}", .{fmtIntSep(123456789)});

    try std.testing.expectEqualStrings("123'456'789", buf.items);
}```
spring stone
#

Awesome. Thanks!

vapid haven
#

just a note: std.fmt.Formatter is nice for the simple case, but for more complex use cases it is often more flexible to just define your own struct, where you can have extra metadata to inform the formatting behaviour (as opposed to only having access to the formatting target).
E.g.

const std = @import("std");
const FmtIntSep = struct {
    n: u64,
    negative: bool,

    pub fn format(
        formatter: FmtIntSep,
        comptime fmt_str: []const u8,
        options: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        // ...
    }
};
fn fmtIntSep(val: anytype) FmtIntSep {
    return .{
        .n = std.math.absCast(val),
        .negative = val < 0,
    };
}

you could also add a field like sep: u8 which would be supplied as an extra argument to fmtIntSep, to allow for different separators. Could also make the struct generic over the integer type. There's a lot of things you can play with.

wary glen
#

[...] can have extra metadata [...]
You can do that with std.fmt.Formatter too, see std.fmt.fmtDuration

#

It becomes somewhat less useful at that point though, so you can use your own struct instead if you prefer

vapid haven