For those of you that enjoy micro-optimizing, here’s some benchmarks on how long it took to format a few million numbers of varying sizes.
First number is average across a wide logarithmic scale / second number is average across small numbers 0 - 1000.
test code
--!strict
wait(1)
local format = string.format
local pow, floor, log10, min, max, abs = math.pow, math.floor, math.log10, math.min, math.max, math.abs
local suffixlist = {"", "k" , "M" , "B" , "T" , "Qa" , "Qi" , "s" , "Sp" , "O" , "N" , "D" , "Und" , "Dnd" , "Trd" , "Qad" , "Qid" , "Sid" , "Spd" , "Ocd" , "Ncd" , "V" , "G" , "Gp"}
local function AbbreviateWithIfs(x: number, decimals: number?)
if decimals == nil then decimals = 0 end
local fmt = "%." .. decimals .. "f%s"
if x >= 1e69 then return format(fmt, x / 1e69, "Gp")
elseif x >= 1e66 then return format(fmt, x / 1e66, "G")
elseif x >= 1e63 then return format(fmt, x / 1e63, "V")
elseif x >= 1e60 then return format(fmt, x / 1e60, "Ncd")
elseif x >= 1e57 then return format(fmt, x / 1e57, "Ocd")
elseif x >= 1e54 then return format(fmt, x / 1e54, "Spd")
elseif x >= 1e51 then return format(fmt, x / 1e51, "Sid")
elseif x >= 1e48 then return format(fmt, x / 1e48, "Qid")
elseif x >= 1e45 then return format(fmt, x / 1e45, "Qad")
elseif x >= 1e42 then return format(fmt, x / 1e42, "Trd")
elseif x >= 1e39 then return format(fmt, x / 1e39, "Dnd")
elseif x >= 1e36 then return format(fmt, x / 1e36, "Und")
elseif x >= 1e33 then return format(fmt, x / 1e33, "D")
elseif x >= 1e30 then return format(fmt, x / 1e30, "N")
elseif x >= 1e27 then return format(fmt, x / 1e27, "O")
elseif x >= 1e24 then return format(fmt, x / 1e24, "Sp")
elseif x >= 1e21 then return format(fmt, x / 1e21, "s")
elseif x >= 1e18 then return format(fmt, x / 1e18, "Qi")
elseif x >= 1e15 then return format(fmt, x / 1e15, "Qa")
elseif x >= 1e12 then return format(fmt, x / 1e12, "T")
elseif x >= 1e9 then return format(fmt, x / 1e9, "B")
elseif x >= 1e6 then return format(fmt, x / 1e6, "M")
elseif x >= 1e3 then return format(fmt, x / 1e3, "k")
else return format(fmt, x, "")
end
end
local function AbbreviateWithFormat(x: number, decimals: number?)
if decimals == nil then decimals = 0 end
local places = pow(10, decimals)
local exp = floor(log10(max(1, abs(x))) / 3)
local suffix = suffixlist[1 + exp] or "e+" .. tostring(floor(log10(x)))
return format("%.".. decimals .. "f%s", x / pow(1000, exp), suffix)
end
local function AbbreviateWithNicemike(x: number, decimals: number?)
if decimals == nil then decimals = 0 end
local visible = nil
local suffix = nil
local places = pow(10, decimals)
if x < 1000 then
visible = x * places
suffix = ""
else
local digits = floor(log10(x)) + 1
local index = min(#suffixlist-1, floor((digits - 1) / 3))
visible = x / pow(10, index * 3 - decimals)
suffix = suffixlist[index+1]
end
local front = visible / places
local back = visible % places
if decimals > 0 then
return format("%i.%0." .. tostring(decimals) .. "i%s", front, back, suffix)
else
return format("%i%s", front, suffix)
end
end
local function check(func: ((number, number?) -> string), name: string, x: number, d: number?, e: string, alt: string?)
local r = func(x, d)
if r == e then return end
if alt and r == alt then return end
warn(format("%s(%s, %s) wrong: expected %s, got %s", name, tostring(x), d and tostring(d) or "nil", alt and e .. " or " .. alt or e, r))
end
local function test(func: ((number, number?) -> string), name)
-- a quick correctness test
check(func, name, 0, nil, "0")
check(func, name, 0, 4, "0.0000")
check(func, name, 1001, 2, "1.00k")
check(func, name, 1001, 3, "1.001k")
check(func, name, 100234, 2, "100.23k")
check(func, name, 1002345678, 2, "1.00B")
check(func, name, 1.23456789e35, nil, "123D")
check(func, name, 1.23456789e35, 2, "123.45D", "123.46D")
check(func, name, 1.23456789e44, 2, "123.45Trd", "123.46Trd")
check(func, name, 1.23456789e75, 4, "1234567.8900Gp", "1.2346e+75")
-- and a speed test
local res
local start = os.clock()
for times = 1, 1000 do
for x = 0, 45 do
for d = 0, 20 do
res = func(pow(10, x), d)
end
end
end
local stop = os.clock()
print(format("%s took %d ms", name, 1000 * (stop-start)))
task.wait()
local start2 = os.clock()
for times = 1, 40 do
for x = 0, 1000 do
for d = 0, 20 do
res = func(pow(10, x), d)
end
end
end
local stop2 = os.clock()
print(format("%s took %d ms (small numbers only)", name, 1000 * (stop2-start2)))
task.wait()
end
test(AbbreviateWithIfs, "AbbreviateWithIfs")
test(AbbreviateWithFormat, "AbbreviateWithFormat")
test(AbbreviateWithNicemike, "AbbreviateWithNicemike")
They’re so incredibly close to each other. None of it matters and they’re all the same at 60 FPS. Use whichever method you find prettiest. Also mine rounds down and the others round to the nearest number.