Neffi
Neffi

Reputation: 11

Format number with variable amount of significant figures depending on size

I've got a little function that displays a formatted amount of some number value. The intention is to show a "commonsense" amount of significant figures depending on the size of the number. So for instance, 1,234 comes out as 1.2k while 12,345 comes out as 12k and 123,456 comes out as 123k.

So in other words, I want to show a single decimal when on the lower end of a given order of magnitude, but not for larger values where it would just be useless noise.

I need this function to scale all the way from 1 to a few billion. The current solution is just to branch it:

-- given `current`
local text = (
  current > 9,999,999,999 and ('%dB')  :format(current/1,000,000,000) or
  current >   999,999,999 and ('%.1fB'):format(current/1,000,000,000) or
  current >     9,999,999 and ('%dM')  :format(current/1,000,000) or
  current >       999,999 and ('%.1fM'):format(current/1,000,000) or
  current >         9,999 and ('%dk')  :format(current/1,000) or
  current >           999 and ('%.1fk'):format(current/1,000) or
  ('%d'):format(current)  -- show values < 1000 floored
)
textobject:SetText(text)

-- code formatted for readability

Which I feel is very ugly. Is there some elegant formula for rounding numbers in this fashion without just adding another (two) clauses for every factor of 1000 larger I need to support?

Upvotes: 0

Views: 95

Answers (2)

Neffi
Neffi

Reputation: 11

I didn't realize how simple this actually was until a friend gave me a solution (which checked the magnitude of the number based on its length). I converted that to use log to find the magnitude, and now have an elegant working answer:

local suf = {'k','M','B','T'}
local function clean_format(val)
  if val == 0 then return '0' end  -- *Edit*: Fix an error caused by attempting to get log10(0)
  local m = math.min(#suf,math.floor(math.log10(val)/3))  -- find the magnitude, or use the max magnitude we 'understand'
  local n = val / 1000 ^ m                                -- calculate the displayed value
  local fmt = (m == 0 or n >= 10) and '%d%s' or '%.1f%s'  -- and choose whether to apply a decimal place based on its size and magnitude
  return fmt:format(n,suf[m] or '')
end

Scaling it up to support a greater factor of 1000 is as easy as putting the next entry in the suf array.

Note: for language-agnostic purposes, Lua arrays are 1-based, not zero based. The above solution would present an off-by-one error in many other languages.

Upvotes: 1

hjpotter92
hjpotter92

Reputation: 80649

Put your ranges and their suffixes inside a table.

local multipliers = {
  {10^10, 'B', 10^9},
  {10^9, 'B', 10^9, true},
  {10^7, 'M', 10^6},
  {10^6, 'M', 10^6, true},
  {10^4, 'k', 10^3},
  {10^3, 'k', 10^3, true},
  {1, '', 1},
}

The optional true value at the 4th position of alternate variables is for the %.1f placeholder. The third index is for the divisor.

Now, iterate over this table (using ipairs) and format accordingly:

function MyFormatter( current )
    for i, t in ipairs( multipliers ) do
      if current >= t[1] then
        local sHold = (t[4] and "%.1f" or "%d")..t[2]
        return sHold:format( current/t[3] )
      end
    end
end

Upvotes: 0

Related Questions