From 90b43755b87ffe771e40a2ffbe70d4fe9ed15628 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Sat, 3 Aug 2024 15:37:52 +0200 Subject: [PATCH] Use integer formatting instead of floating points This avoids embedding Zig's floating point formatting tables and ancillary code, shaving 17k off the final static binary for x86_64. Also adjusted the cut-off points for the units to be more precise. --- src/browser.zig | 19 ++++++---- src/ui.zig | 99 ++++++++++++++++++++++++++++++++++++------------- src/util.zig | 31 ++++++++++++++++ 3 files changed, 115 insertions(+), 34 deletions(-) diff --git a/src/browser.zig b/src/browser.zig index 177be49..1a1188a 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -216,10 +216,13 @@ const Row = struct { ui.addch('['); if (main.config.show_percent) { self.bg.fg(.num); - ui.addprint("{d:>5.1}", .{ 100 * - if (main.config.show_blocks) @as(f32, @floatFromInt(item.pack.blocks)) / @as(f32, @floatFromInt(@max(1, dir_parent.entry.pack.blocks))) - else @as(f32, @floatFromInt(item.size)) / @as(f32, @floatFromInt(@max(1, dir_parent.entry.size))) - }); + var num : u64 = if (main.config.show_blocks) item.pack.blocks else item.size; + var denom : u64 = if (main.config.show_blocks) dir_parent.entry.pack.blocks else dir_parent.entry.size; + if (num > (1<<54)) { // avoid overflow + num >>= 10; + denom >>= 10; + } + ui.addstr(&util.fmt5dec(@intCast( (num * 1000 + (denom / 2)) / denom ))); self.bg.fg(.default); ui.addch('%'); } @@ -261,12 +264,12 @@ const Row = struct { ui.addnum(self.bg, n); } else if (n < 100_000) ui.addnum(self.bg, n) - else if (n < 1000_000) { - ui.addprint("{d:>5.1}", .{ @as(f32, @floatFromInt(n)) / 1000 }); + else if (n < 999_950) { + ui.addstr(&util.fmt5dec(@intCast( (n + 50) / 100 ))); self.bg.fg(.default); ui.addch('k'); - } else if (n < 1000_000_000) { - ui.addprint("{d:>5.1}", .{ @as(f32, @floatFromInt(n)) / 1000_000 }); + } else if (n < 999_950_000) { + ui.addstr(&util.fmt5dec(@intCast( (n + 50_000) / 100_000 ))); self.bg.fg(.default); ui.addch('M'); } else { diff --git a/src/ui.zig b/src/ui.zig index 10d39ce..3d3eb90 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -415,39 +415,86 @@ pub fn addch(ch: c.chtype) void { // unit = " XB" or " XiB" // Concatenated, these take 8 columns in SI mode or 9 otherwise. pub const FmtSize = struct { - buf: [8:0]u8, + buf: [5:0]u8, unit: [:0]const u8, - pub fn fmt(v: u64) @This() { - var r: @This() = undefined; - var f: f32 = @floatFromInt(v); - if (main.config.si) { - if(f < 1000.0) { r.unit = " B"; } - else if(f < 1e6) { r.unit = " KB"; f /= 1e3; } - else if(f < 1e9) { r.unit = " MB"; f /= 1e6; } - else if(f < 1e12) { r.unit = " GB"; f /= 1e9; } - else if(f < 1e15) { r.unit = " TB"; f /= 1e12; } - else if(f < 1e18) { r.unit = " PB"; f /= 1e15; } - else { r.unit = " EB"; f /= 1e18; } - } - else { - if(f < 1000.0) { r.unit = " B"; } - else if(f < 1023e3) { r.unit = " KiB"; f /= 1024.0; } - else if(f < 1023e6) { r.unit = " MiB"; f /= 1048576.0; } - else if(f < 1023e9) { r.unit = " GiB"; f /= 1073741824.0; } - else if(f < 1023e12) { r.unit = " TiB"; f /= 1099511627776.0; } - else if(f < 1023e15) { r.unit = " PiB"; f /= 1125899906842624.0; } - else { r.unit = " EiB"; f /= 1152921504606846976.0; } - } - _ = std.fmt.bufPrintZ(&r.buf, "{d:>5.1}", .{f}) catch unreachable; - return r; + fn init(u: [:0]const u8, n: u64, mul: u64, div: u64) FmtSize { + return .{ + .unit = u, + .buf = util.fmt5dec(@intCast( ((n*mul) +| (div / 2)) / div )), + }; } - pub fn num(self: *const @This()) [:0]const u8 { - return std.mem.sliceTo(&self.buf, 0); + pub fn fmt(v: u64) FmtSize { + if (main.config.si) { + if (v < 1000) { return FmtSize.init(" B", v, 10, 1); } + else if (v < 999_950) { return FmtSize.init(" KB", v, 1, 100); } + else if (v < 999_950_000) { return FmtSize.init(" MB", v, 1, 100_000); } + else if (v < 999_950_000_000) { return FmtSize.init(" GB", v, 1, 100_000_000); } + else if (v < 999_950_000_000_000) { return FmtSize.init(" TB", v, 1, 100_000_000_000); } + else if (v < 999_950_000_000_000_000) { return FmtSize.init(" PB", v, 1, 100_000_000_000_000); } + else { return FmtSize.init(" EB", v, 1, 100_000_000_000_000_000); } + } else { + // Cutoff values are obtained by calculating 999.949999999999999999999999 * div with an infinite-precision calculator. + // (Admittedly, this precision is silly) + if (v < 1000) { return FmtSize.init(" B", v, 10, 1); } + else if (v < 1023949) { return FmtSize.init(" KiB", v, 10, 1<<10); } + else if (v < 1048523572) { return FmtSize.init(" MiB", v, 10, 1<<20); } + else if (v < 1073688136909) { return FmtSize.init(" GiB", v, 10, 1<<30); } + else if (v < 1099456652194612) { return FmtSize.init(" TiB", v, 10, 1<<40); } + else if (v < 1125843611847281869) { return FmtSize.init(" PiB", v, 10, 1<<50); } + else { return FmtSize.init(" EiB", v, 1, (1<<60)/10); } + } + } + + pub fn num(self: *const FmtSize) [:0]const u8 { + return &self.buf; + } + + fn testEql(self: FmtSize, exp: []const u8) !void { + var buf: [10]u8 = undefined; + try std.testing.expectEqualStrings(exp, try std.fmt.bufPrint(&buf, "{s}{s}", .{ self.num(), self.unit })); } }; +test "fmtsize" { + main.config.si = true; + try FmtSize.fmt( 0).testEql(" 0.0 B"); + try FmtSize.fmt( 999).testEql("999.0 B"); + try FmtSize.fmt( 1000).testEql(" 1.0 KB"); + try FmtSize.fmt( 1049).testEql(" 1.0 KB"); + try FmtSize.fmt( 1050).testEql(" 1.1 KB"); + try FmtSize.fmt( 999_899).testEql("999.9 KB"); + try FmtSize.fmt( 999_949).testEql("999.9 KB"); + try FmtSize.fmt( 999_950).testEql(" 1.0 MB"); + try FmtSize.fmt( 1000_000).testEql(" 1.0 MB"); + try FmtSize.fmt( 999_850_009).testEql("999.9 MB"); + try FmtSize.fmt( 999_899_999).testEql("999.9 MB"); + try FmtSize.fmt( 999_900_000).testEql("999.9 MB"); + try FmtSize.fmt( 999_949_999).testEql("999.9 MB"); + try FmtSize.fmt( 999_950_000).testEql(" 1.0 GB"); + try FmtSize.fmt( 999_999_999).testEql(" 1.0 GB"); + try FmtSize.fmt(std.math.maxInt(u64)).testEql(" 18.4 EB"); + + main.config.si = false; + try FmtSize.fmt( 0).testEql(" 0.0 B"); + try FmtSize.fmt( 999).testEql("999.0 B"); + try FmtSize.fmt( 1000).testEql(" 1.0 KiB"); + try FmtSize.fmt( 1024).testEql(" 1.0 KiB"); + try FmtSize.fmt( 102400).testEql("100.0 KiB"); + try FmtSize.fmt( 1023898).testEql("999.9 KiB"); + try FmtSize.fmt( 1023949).testEql(" 1.0 MiB"); + try FmtSize.fmt( 1048523571).testEql("999.9 MiB"); + try FmtSize.fmt( 1048523572).testEql(" 1.0 GiB"); + try FmtSize.fmt( 1073688136908).testEql("999.9 GiB"); + try FmtSize.fmt( 1073688136909).testEql(" 1.0 TiB"); + try FmtSize.fmt( 1099456652194611).testEql("999.9 TiB"); + try FmtSize.fmt( 1099456652194612).testEql(" 1.0 PiB"); + try FmtSize.fmt(1125843611847281868).testEql("999.9 PiB"); + try FmtSize.fmt(1125843611847281869).testEql(" 1.0 EiB"); + try FmtSize.fmt(std.math.maxInt(u64)).testEql(" 16.0 EiB"); +} + // Print a formatted human-readable size string onto the given background. pub fn addsize(bg: Bg, v: u64) void { const r = FmtSize.fmt(v); diff --git a/src/util.zig b/src/util.zig index bc486d9..d571136 100644 --- a/src/util.zig +++ b/src/util.zig @@ -37,6 +37,37 @@ pub fn arrayListBufZ(buf: *std.ArrayList(u8)) [:0]const u8 { return buf.items[0..buf.items.len-1:0]; } +// Format an integer as right-aligned '###.#'. +// Pretty much equivalent to: +// std.fmt.bufPrintZ(.., "{d:>5.1}", @floatFromInt(n)/10.0); +// Except this function doesn't pull in large float formatting tables. +pub fn fmt5dec(n: u14) [5:0]u8 { + std.debug.assert(n <= 9999); + var buf: [5:0]u8 = " 0.0".*; + var v = n; + buf[4] += @intCast(v % 10); + v /= 10; + buf[2] += @intCast(v % 10); + v /= 10; + if (v == 0) return buf; + buf[1] = '0' + @as(u8, @intCast(v % 10)); + v /= 10; + if (v == 0) return buf; + buf[0] = '0' + @as(u8, @intCast(v)); + return buf; +} + +test "fmt5dec" { + const eq = std.testing.expectEqualStrings; + try eq(" 0.0", &fmt5dec(0)); + try eq(" 0.5", &fmt5dec(5)); + try eq(" 9.5", &fmt5dec(95)); + try eq(" 12.5", &fmt5dec(125)); + try eq("123.9", &fmt5dec(1239)); + try eq("999.9", &fmt5dec(9999)); +} + + // Straightforward Zig port of strnatcmp() from https://github.com/sourcefrog/natsort/ // (Requiring nul-terminated strings is ugly, but we've got them anyway and it does simplify the code) pub fn strnatcmp(a: [:0]const u8, b: [:0]const u8) std.math.Order {