Skip to content

Commit

Permalink
feat(stringFunctions): add string.formatSI to replace several similar…
Browse files Browse the repository at this point in the history
… functions (#2768)

* refactor(numberfunctions): remove unused number formatting functions

These functions have no existing uses in this repo, and weren't even
possible to call in most lua code, since they are not added into widget
environments.

* style(stringFunctions): fix whitespace

* feat(stringFunctions): add string.formatSI

formatSI provides a standard way to concisely format numbers of
effectively arbitrary scale. This can replace many uses of custom
functions both in this repo, and in user widgets.

* refactor: use formatSI instead of various custom formatters
  • Loading branch information
salinecitrine authored Mar 24, 2024
1 parent 9906a6a commit f724367
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 134 deletions.
56 changes: 0 additions & 56 deletions common/numberfunctions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,62 +16,6 @@ if not math.cross_product then
end
end

local abs = math.abs
local strFormat = string.format

if not ToSI then
function ToSI(num, displaySign)
if type(num) ~= 'number' then
num = tonumber(num)
end
if num == 0 then
return "0"
else
local absNum = abs(num)
if absNum < 0.001 then
return displaySign and strFormat("%+.1fu", 1000000 * num) or strFormat("%.1fu", 1000000 * num)
elseif absNum < 1 then
return displaySign and strFormat("%+.1f", num) or strFormat("%.1f", num)
elseif absNum < 1000 then
return displaySign and strFormat("%+.0f", num) or strFormat("%.0f", num)
elseif absNum < 1000000 then
return displaySign and strFormat("%+.1fk", 0.001 * num) or strFormat("%.1fk", 0.001 * num)
else
return displaySign and strFormat("%+.1fM", 0.000001 * num) or strFormat("%.1fM", 0.000001 * num)
end
end
end
end

if not ToSIPrec then
function ToSIPrec(num)
-- more precise
if type(num) ~= 'number' then
num = tonumber(num)
end

if num == 0 then
return "0"
else
local absNum = abs(num)
if absNum < 0.001 then
return strFormat("%.2fu", 1000000 * num)
elseif absNum < 1 then
return strFormat("%.2f", num)
elseif absNum < 10 then
return strFormat("%.2f", num)

elseif absNum < 1000 then
return strFormat("%.0f", num)
elseif absNum < 1000000 then
return strFormat("%.1fk", 0.001 * num)
else
return strFormat("%.1fM", 0.000001 * num)
end
end
end
end

if not math.triangulate then
-- accepts an array of polygons (where a polygon is an array of {x, z} vertices), and returns an array of counterclockwise triangles
function math.triangulate(polies)
Expand Down
85 changes: 81 additions & 4 deletions common/stringFunctions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ if not string.lines then
end

-- Returns python style tuple string.partition()
if not string.partition then
if not string.partition then
function string:partition(sep)
local seppos = self:find(sep, nil, true)
if seppos == nil then
return self, nil, nil
if seppos == nil then
return self, nil, nil
else
if seppos == 1 then
if seppos == 1 then
return nil, sep, self:sub(sep:len()+1)
else
return self:sub(1, seppos -1), sep, self:sub(seppos + sep:len())
Expand Down Expand Up @@ -78,4 +78,81 @@ end
-- print(string.partition("blaksjdfsaldkj","ldkj"))
-- print(string.partition("blaksjdfsaldkj","aks"))

if not string.formatSI then
local mathFloor = math.floor
local mathLog10 = math.log10
local mathPow = math.pow
local stringFormat = string.format

local SI_PREFIXES_LOG1K = {
[10] = "Q",
[9] = "R",
[8] = "Y",
[7] = "Z",
[6] = "E",
[5] = "P",
[4] = "T",
[3] = "G",
[2] = "M",
[1] = "k",
[0] = "",
[-1] = "m",
[-2] = "u",
[-3] = "n",
[-4] = "p",
[-5] = "f",
[-6] = "a",
[-7] = "z",
[-8] = "y",
[-9] = "r",
[-10] = "q",
}

local DIVIDE_LOG1K = {}
for i = -10, 10 do
DIVIDE_LOG1K[i] = 1 / mathPow(1000, i)
end

--- Formats a number with an SI prefix, and at most 3 significant figures
---@param number number
---@param options table Optional parameters for formatting
---@param options.leaveTrailingZeros boolean Whether to leave any trailing zeros (default: false)
---@param options.showSign boolean Whether to show a "+" for positive numbers (default: false)
---@return string
function string.formatSI(number, options)
if number == nil then
return nil
end

if number == 0 then
return "0"
end

local sign = 1
if number < 0 then
number = number * -1
sign = -1
end

local numberLog10 = mathLog10(number)
local numberLog1k = mathFloor(numberLog10 / 3 + 1e-4)
local siPrefix = SI_PREFIXES_LOG1K[numberLog1k]
if siPrefix == nil then
return nil
end

local precision = 2 - mathFloor(numberLog10 - 3 * numberLog1k)

local str = stringFormat("%." .. precision .. "f", sign * number * DIVIDE_LOG1K[numberLog1k])

if precision > 0 and not (options and options.leaveTrailingZeros) then
str = str:gsub("%.?0+$", "")
end

if sign == 1 and options and options.showSign then
str = "+" .. str
end

return str .. siPrefix
end
end
24 changes: 14 additions & 10 deletions luaui/Widgets/gui_advplayerslist.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2363,24 +2363,28 @@ function DrawResources(energy, energyStorage, energyShare, energyConversion, met
end
end

local function formatRes(number)
if number < 1000 then
return string.format("%d", number)
else
return string.format("%.1fk", number / 1000)
end
end

function DrawIncome(energy, metal, posY, dead)
local fontsize = dead and 4.5 or 8.5
local sizeMult = playerScale + ((1-playerScale)*0.33)
fontsize = fontsize * sizeMult
font:Begin()
if energy > 0 then
font:Print('\255\255\255\050'..formatRes(math.floor(energy)), m_income.posX + widgetPosX + m_income.width - 2, posY + ((fontsize*0.2)*sizeMult) + (dead and 1 or 0), fontsize, "or")
font:Print(
'\255\255\255\050' .. string.formatSI(math.floor(energy)),
m_income.posX + widgetPosX + m_income.width - 2,
posY + ((fontsize*0.2)*sizeMult) + (dead and 1 or 0),
fontsize,
"or"
)
end
if metal > 0 then
font:Print('\255\235\235\235'..formatRes(math.floor(metal)), m_income.posX + widgetPosX + m_income.width - 2, posY + ((fontsize*1.15)*sizeMult) + (dead and 1 or 0), fontsize, "or")
font:Print(
'\255\235\235\235' .. string.formatSI(math.floor(metal)),
m_income.posX + widgetPosX + m_income.width - 2,
posY + ((fontsize*1.15)*sizeMult) + (dead and 1 or 0),
fontsize,
"or"
)
end
font:End()
end
Expand Down
17 changes: 2 additions & 15 deletions luaui/Widgets/gui_ecostats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,6 @@ for udefID, def in ipairs(UnitDefs) do
end
end

local function round(num, idp)
local mult = 10 ^ (idp or 0)
return floor(num * mult + 0.5) / mult
end

local function formatRes(number)
if number < 1000 then
return string.format("%d", number)
else
return string.format("%.1fk", number / 1000)
end
end

local function getTeamSum(allyIndex, param)
local tValue = 0
local teamList = allyData[allyIndex]["teams"]
Expand Down Expand Up @@ -677,7 +664,7 @@ end

local function DrawEText(numberE, vOffset)
if cfgResText then
local label = tconcat({ "", formatRes(numberE) })
local label = string.formatSI(numberE)
font:Begin()
font:SetTextColor({ 1, 1, 0, 1 })
font:Print(label, widgetPosX + widgetWidth - (5 * sizeMultiplier), widgetPosY + widgetHeight - vOffset + (tH * 0.22), tH / 2.3, 'rs')
Expand All @@ -688,7 +675,7 @@ end
local function DrawMText(numberM, vOffset)
vOffset = vOffset - (borderPadding * 0.5)
if cfgResText then
local label = tconcat({ "", formatRes(numberM) })
local label = string.formatSI(numberM)
font:Begin()
font:SetTextColor({ 0.85, 0.85, 0.85, 1 })
font:Print(label, widgetPosX + widgetWidth - (5 * sizeMultiplier), widgetPosY + widgetHeight - vOffset + (tH * 0.58), tH / 2.3, 'rs')
Expand Down
11 changes: 1 addition & 10 deletions luaui/Widgets/gui_reclaim_field_highlight.lua
Original file line number Diff line number Diff line change
Expand Up @@ -873,16 +873,7 @@ local function DrawFeatureClusterText()
fontSize = max(fontSize, fontSizeMin)
fontSize = min(fontSize, fontSizeMax)

local metal = featureClusters[i].metal
local metalText

if metal < 1000 then
metalText = string.format("%.0f", metal) --exact number
elseif metal < 10000 then
metalText = string.format("%.1fK", math.floor(metal / 100) / 10) --4.5K
else
metalText = string.format("%.0fK", math.floor(metal / 1000)) --40K
end
local metalText = string.formatSI(featureClusters[i].metal)

glColor(numberColor)

Expand Down
44 changes: 5 additions & 39 deletions luaui/Widgets/gui_teamstats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -110,42 +110,6 @@ local anonymousTeamColor = {Spring.GetConfigInt("anonymousColorR", 255)/255, Spr

local isSpec = Spring.GetSpectatingState()

function roundNumber(num,useFirstDecimal)
return useFirstDecimal and format("%0.1f",round(num,1)) or round(num)
end

function convertSIPrefix(value,thresholdmodifier,noSmallValues,useFirstDecimal)
if value == 0 or not value then
return value
end
local halfScale = ceil(#SIsuffixes/2)
if useFirstDecimal then
useFirstDecimal = useFirstDecimal + halfScale
end
useFirstDecimal = useFirstDecimal or #SIsuffixes+1
local sign = value > 0
value = abs(value)
thresholdmodifier = thresholdmodifier or 1
local startIndex = 1
if noSmallValues then
startIndex = halfScale
end
local baseVal = 10^(-12)
local retVal = ""
for i=startIndex, #SIsuffixes do
local tenPower = baseVal*10^(3*(i-1))
local compareVal = baseVal*10^(3*i) * thresholdmodifier
if value < compareVal then
retVal = roundNumber(value/tenPower,i>=useFirstDecimal) .. SIsuffixes[i]
break
end
end
if not sign then
retVal = "-" .. retVal
end
return retVal
end

function aboveRectangle(mousePos,boxData)
local included = true
for coordName, coordData in pairs(boxData.absSizes) do
Expand Down Expand Up @@ -672,11 +636,13 @@ function ReGenerateTextDisplayList()
if value == huge or value == -huge then
value = "-"
elseif tonumber(value) then
local v = tonumber(value)
if varName:sub(1,5) ~= "units" and varName ~= "aggressionLevel" then
value = convertSIPrefix(tonumber(value),1,true,1)
else
value = convertSIPrefix(tonumber(value))
if v and math.abs(v) < 1 then
v = 0
end
end
value = string.formatSI(v)
end
if varName == "damageEfficiency" or varName == "killEfficiency" then
value = value .. "%"
Expand Down

0 comments on commit f724367

Please sign in to comment.