util.human.units: A library for formatting numbers with SI units
authorKim Alvefur <zash@zash.se>
Fri, 04 Jan 2019 08:46:26 +0100
changeset 10890 994c4a333199
parent 10889 2f751880767c
child 10891 3debe04a6162
util.human.units: A library for formatting numbers with SI units
spec/util_human_units_spec.lua
util/human/units.lua
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/util_human_units_spec.lua	Fri Jan 04 08:46:26 2019 +0100
@@ -0,0 +1,15 @@
+local units = require "util.human.units";
+
+describe("util.human.units", function ()
+	describe("format", function ()
+		it("formats numbers with SI units", function ()
+			assert.equal("1 km", units.format(1000, "m"));
+			assert.equal("1 GJ", units.format(1000000000, "J"));
+			assert.equal("1 ms", units.format(1/1000, "s"));
+			assert.equal("10 ms", units.format(10/1000, "s"));
+			assert.equal("1 ns", units.format(1/1000000000, "s"));
+			assert.equal("1 KiB", units.format(1024, "B", 'b'));
+			assert.equal("1 MiB", units.format(1024*1024, "B", 'b'));
+		end);
+	end);
+end);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/human/units.lua	Fri Jan 04 08:46:26 2019 +0100
@@ -0,0 +1,58 @@
+local large = {
+	"k", 1000,
+	"M", 1000000,
+	"G", 1000000000,
+	"T", 1000000000000,
+	"P", 1000000000000000,
+	"E", 1000000000000000000,
+	"Z", 1000000000000000000000,
+	"Y", 1000000000000000000000000,
+}
+local small = {
+	"m", 0.001,
+	"μ", 0.000001,
+	"n", 0.000000001,
+	"p", 0.000000000001,
+	"f", 0.000000000000001,
+	"a", 0.000000000000000001,
+	"z", 0.000000000000000000001,
+	"y", 0.000000000000000000000001,
+}
+
+local binary = {
+	"Ki", 2^10,
+	"Mi", 2^20,
+	"Gi", 2^30,
+	"Ti", 2^40,
+	"Pi", 2^50,
+	"Ei", 2^60,
+	"Zi", 2^70,
+	"Yi", 2^80,
+}
+
+-- n: number, the number to format
+-- unit: string, the base unit
+-- b: optional enum 'b', thousands base
+local function format(n, unit, b) --> string
+	local round = math.floor;
+	local prefixes = large;
+	local logbase = 1000;
+	local fmt = "%.3g %s%s";
+	if n == 0 then
+		return fmt:format(n, "", unit);
+	end
+	if b == 'b' then
+		prefixes = binary;
+		logbase = 1024;
+	elseif n < 1 then
+		prefixes = small;
+		round = math.ceil;
+	end
+	local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase)))));
+	local prefix, multiplier = table.unpack(prefixes, m * 2-1, m*2);
+	return fmt:format(n / (multiplier or 1), prefix or "", unit);
+end
+
+return {
+	format = format;
+};