Skip to content

Instantly share code, notes, and snippets.

@rzuf79
Last active April 29, 2024 12:50
Show Gist options
  • Save rzuf79/7f3b7628533e0b3a140da55186963022 to your computer and use it in GitHub Desktop.
Save rzuf79/7f3b7628533e0b3a140da55186963022 to your computer and use it in GitHub Desktop.
Hnum - a barbaric large number module for games. Stores obscenely large numbers for you and spits out nicely formatted strings for displaying them in a human-readable formak like "1.5k". Supports arithmetic operations and whatnot.
--[[
Hnum - a barbaric large number module for games.
Usage:
local a_hundred = HNum:new(100)
print(a_hundred:get_formatted()) -- 100
local a_thousand = HNum:new(1000)
print(a_thousand:get_formatted()) -- 1k
local also_a_thousand = HNum:new(1, 1)
print(also_a_thousand:get_formatted()) -- 1k
a_thousand = a_thousand + 1000
print(a_thousand:get_formatted()) -- 2k
local a_million = HNum:new(1, 2)
print(a_million:get_formatted()) -- 1m
local two_billion_from_string = HNum:new("2573684736")
print(two_billion_from_string:get_formatted()) -- 2.57b
print(two_billion_from_string:to_string()) -- 2573684000
Note, that some precission is lost in the last example. It can be tweaked by increasing the
'precision' variable in HNum:from_string().
The methods for converting the number to and from a string are here to make storing the
value easily when saving the game. Simply store the :to_string-ed value in your save
data and pass it when creating a new instance of HNum during load.
]]--
local HNum = {}
-- Convert a number to a HNum table if needed
local function convert(num)
if type(num) == "number" then
num = HNum:new(num, 0)
end
return num
end
function HNum:new(value, thousands)
local newObj = {
__type = "hnum",
value = value or 0.0,
thousands = thousands or 0
}
self.__index = self
setmetatable(newObj, self)
if type(value) == "string" then
newObj:from_string(value)
end
newObj:fix_compression()
return newObj
end
function HNum:to_string()
local outText = tostring(self.value)
local charsCount = string.len(tostring(math.floor(self.value))) + (self.thousands * 3)
local dotIdx = string.find(outText, "%.")
if dotIdx ~= nil then
outText = string.sub(outText, 1, dotIdx - 1) .. string.sub(outText, dotIdx + 1)
end
if string.len(outText) > charsCount then
outText = string.sub(outText, 1, charsCount)
end
while string.len(outText) < charsCount do
outText = outText .. "0"
end
return outText
end
function HNum:from_string(value)
local precision = 2
self.thousands = math.floor((string.len(value) - 1) / 3)
if self.thousands == 0 then
self.value = tonumber(value)
self.thousands = 0
end
local baseValue = string.sub(value, 1, string.len(value) - (self.thousands * 3))
self.value = tonumber(baseValue)
precision = math.min(self.thousands, precision)
for i = 0, precision - 1 do
local subString = string.sub(value, string.len(baseValue) + (i * 3) + 1, string.len(baseValue) + (i * 3) + 3)
self.value = self.value + tonumber(subString) / math.pow(1000.0, i + 1)
end
end
function HNum:fix_compression()
while math.abs(self.value) >= 1000.0 or self.thousands < 0 do
self.value = self.value / 1000.0
self.thousands = self.thousands + 1
end
while math.abs(self.value) < 1.0 and self.thousands > 0 do
self.value = self.value * 1000.0
self.thousands = self.thousands - 1
end
end
function HNum:get_formatted()
local outAbbrev = ""
local abbrevs = { "", "k", "m", "b", "t" }
if self.thousands == 0 then
return tostring(math.floor(self.value+0.5))
elseif self.thousands < #abbrevs then
outAbbrev = abbrevs[self.thousands + 1]
else
local firstLetterCharCode = 97
local lastLetterCharCode = 123
local abbrevIdx = self.thousands - #abbrevs
outAbbrev = string.char(firstLetterCharCode + abbrevIdx % (lastLetterCharCode - firstLetterCharCode))
abbrevIdx = math.floor(abbrevIdx / (lastLetterCharCode - firstLetterCharCode))
outAbbrev = "" .. string.char(firstLetterCharCode + abbrevIdx % (lastLetterCharCode - firstLetterCharCode)) .. outAbbrev
end
local truncate_mult = (10 ^ 2)
local truncated_val = math.floor(self.value * truncate_mult) /truncate_mult
local out = string.format("%.2f", truncated_val)
if self.thousands > 0 then
out = out:gsub("%.?0*$", "")
end
return out .. outAbbrev
end
function HNum:to_float()
local outVal = self.value
for i = 1, self.thousands do
outVal = outVal * 1000
end
return outVal
end
function HNum:is_less_than(other)
other = convert(other)
return other.thousands > self.thousands or (other.thousands == self.thousands and other.value > self.value)
end
function HNum:equals(other)
other = convert(other)
return self.thousands == other.thousands and math.abs(self.value - other.value) < 0.00001
end
function HNum.__add(left_side, right_side)
left_side = convert(left_side)
right_side = convert(right_side)
HNum.align_values(left_side, right_side)
return HNum:new(left_side.value + right_side.value, left_side.thousands)
end
function HNum.__sub(left_side, right_side)
left_side = convert(left_side)
right_side = convert(right_side)
HNum.align_values(left_side, right_side)
return HNum:new(left_side.value - right_side.value, left_side.thousands)
end
function HNum.__mul(left_side, right_side)
left_side = convert(left_side)
right_side = convert(right_side)
return HNum:new(left_side.value * right_side.value, left_side.thousands + right_side.thousands)
end
function HNum.__div(left_side, right_side)
left_side = convert(left_side)
right_side = convert(right_side)
return HNum:new(left_side.value / right_side.value, left_side.thousands - right_side.thousands)
end
function HNum.__eq(left_side, right_side)
return left_side:queals(right_side)
end
function HNum.__lt(left_side, right_side) -- less than
return left_side.is_less_than(right_side)
end
function HNum.__le(left_side, right_side) -- less or equal
return left_side:is_less_than(right_side) or left_side:equals(right_side)
end
function HNum.align_values(first, second)
first = convert(first)
second = convert(second)
if first.thousands == second.thousands then
return
end
if first.thousands > second.thousands then
local diff = first.thousands - second.thousands
for i = 1, diff do
second.value = second.value / 1000.0
end
second.thousands = first.thousands
else
local diff = second.thousands - first.thousands
for i = 1, diff do
first.value = first.value / 1000.0
end
first.thousands = second.thousands
end
end
return HNum
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment