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.
This commit is contained in:
Yorhel 2024-08-03 15:37:52 +02:00
parent 8ad61e87c1
commit 90b43755b8
3 changed files with 115 additions and 34 deletions

View file

@ -216,10 +216,13 @@ const Row = struct {
ui.addch('['); ui.addch('[');
if (main.config.show_percent) { if (main.config.show_percent) {
self.bg.fg(.num); self.bg.fg(.num);
ui.addprint("{d:>5.1}", .{ 100 * var num : u64 = if (main.config.show_blocks) item.pack.blocks else item.size;
if (main.config.show_blocks) @as(f32, @floatFromInt(item.pack.blocks)) / @as(f32, @floatFromInt(@max(1, dir_parent.entry.pack.blocks))) var denom : u64 = if (main.config.show_blocks) dir_parent.entry.pack.blocks else dir_parent.entry.size;
else @as(f32, @floatFromInt(item.size)) / @as(f32, @floatFromInt(@max(1, 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); self.bg.fg(.default);
ui.addch('%'); ui.addch('%');
} }
@ -261,12 +264,12 @@ const Row = struct {
ui.addnum(self.bg, n); ui.addnum(self.bg, n);
} else if (n < 100_000) } else if (n < 100_000)
ui.addnum(self.bg, n) ui.addnum(self.bg, n)
else if (n < 1000_000) { else if (n < 999_950) {
ui.addprint("{d:>5.1}", .{ @as(f32, @floatFromInt(n)) / 1000 }); ui.addstr(&util.fmt5dec(@intCast( (n + 50) / 100 )));
self.bg.fg(.default); self.bg.fg(.default);
ui.addch('k'); ui.addch('k');
} else if (n < 1000_000_000) { } else if (n < 999_950_000) {
ui.addprint("{d:>5.1}", .{ @as(f32, @floatFromInt(n)) / 1000_000 }); ui.addstr(&util.fmt5dec(@intCast( (n + 50_000) / 100_000 )));
self.bg.fg(.default); self.bg.fg(.default);
ui.addch('M'); ui.addch('M');
} else { } else {

View file

@ -415,39 +415,86 @@ pub fn addch(ch: c.chtype) void {
// unit = " XB" or " XiB" // unit = " XB" or " XiB"
// Concatenated, these take 8 columns in SI mode or 9 otherwise. // Concatenated, these take 8 columns in SI mode or 9 otherwise.
pub const FmtSize = struct { pub const FmtSize = struct {
buf: [8:0]u8, buf: [5:0]u8,
unit: [:0]const u8, unit: [:0]const u8,
pub fn fmt(v: u64) @This() { fn init(u: [:0]const u8, n: u64, mul: u64, div: u64) FmtSize {
var r: @This() = undefined; return .{
var f: f32 = @floatFromInt(v); .unit = u,
if (main.config.si) { .buf = util.fmt5dec(@intCast( ((n*mul) +| (div / 2)) / div )),
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;
} }
pub fn num(self: *const @This()) [:0]const u8 { pub fn fmt(v: u64) FmtSize {
return std.mem.sliceTo(&self.buf, 0); 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. // Print a formatted human-readable size string onto the given background.
pub fn addsize(bg: Bg, v: u64) void { pub fn addsize(bg: Bg, v: u64) void {
const r = FmtSize.fmt(v); const r = FmtSize.fmt(v);

View file

@ -37,6 +37,37 @@ pub fn arrayListBufZ(buf: *std.ArrayList(u8)) [:0]const u8 {
return buf.items[0..buf.items.len-1:0]; 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/ // 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) // (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 { pub fn strnatcmp(a: [:0]const u8, b: [:0]const u8) std.math.Order {