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('[');
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 {

View file

@ -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);

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];
}
// 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 {