How to format numbers like Miners Haven does.


  • This tutorial was sparked by a discussion about how an answer (on the main site) was not the best way to format numbers (due to its lack of generalization).

    First, I'd like to take a moment to talk about generalization. Programmers are all about generalization. Instead of working on the specific case, you can write a general case that will handle all the specific cases easily. In object oriented styles of programming, you can see this in objects. For example,

    Instance.new("Part")
    

    creates a part with a lot of general properties that we want all parts to have, such as Size, Shape, Position, etc. If we had to define what properties, methods, and elements an object has every time we instantiated one, we'd be dead by the time we finished any kind of playable game. That's where generalization comes in. We have one specific case that keeps happening, and, because we're programmers, we make it so that we can be as lazy/fast/efficient as possible while still getting the job done.

    This is where formatting numbers comes in. Often, people will use an if statement for every single number that they want formatted. If you have several sizes of numbers that need formatted, then you're in trouble. I'd hate to write

    if then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    elseif then
    end
    

    even if I was copy pasting. That's where the answer to questions like this one comes in. Instead of checking every number manually, what if we had a loop that could do it for us? What if we could loop through every size of number to check if our number is that size, and, if so, give it that suffix?

    Here's how: First, we need to remember that every level of suffix is a power of ten. 1,000 is a power of ten, 1,000,000 is a power of ten, etc. We just have to find out if our number is between two different powers of ten, and, if so, use the suffix for the lesser power of ten of the two we're between. For example, if we have 99,324, that's less than 1,000,000 and greater than 1,000. We'd choose the lesser, i.e. 1,000's suffix (k). But then we run into an issue: Not every power of ten is the next level of suffix. Every level of suffix is a power of ten, but it doesn't go both ways. For instance, 10,000 is of the same level as 1,000. To get around this, we just need to remember that each level of suffix is a power of ten that has a number in the exponent divisible by 3.

    10^3 = 1,000
    10^6 = 1,000,000
    10^9 = 1,000,000,000
    --# etc
    

    Now we have a general idea of how to construct our loop. Let's count by threes up to our highest suffix, and calculate our suffix that way (assume that x is the power of ten that is equivalent to your highest suffix, i.e. x = 12 would be a suffix for a trillion):

    for i = 3, x, 3 do	
    	
    end
    

    But wait, how do we figure out if we're at our suffix and before the next one? It's actually pretty simple. We take 10^i and 10^(i + 3) and, check to see if our number is between them (I'll add some variables, too. You'll see their use later):

    for i = 3, x, 3 do	
    
    	local amount_label_found = false
    	local string_number
    
    	if number >= (10 ^ i) and number < (10 ^ (i + 3)) then
    		--# we know this is our suffix area
    	end
    
    end
    

    Now we have to get our suffix, get the right quantity of numbers in our display string (so that 333k shows up and 32.5k show up properly. Remember that 32.5 counts as four characters, so just checking the length of the string is not sufficient). The following code handles that:

    local amount_label_found = false
    local string_number
    
    for i = 3, x, 3 do
    
    	if number >= (10 ^ i) and number < (10 ^ (i + 3)) then
    	
    		amount_label_found = true --# we found our suffix area
    
    		local unchecked_m_variable = (math.floor(number / (10 ^ (i - 2))) / 100)	
    		local string_m_variable = tostring(unchecked_m_variable)
    		local decimal_check = string.find(string_m_variable, "%.")
    		local m_variable = string.sub(string_m_variable, 1, (decimal_check and decimal_check ~= 4) and 4 or 3)
    	
    	end
    end
    

    unchecked_m_variable is just the original number truncated. Then we change it to a string and check to see if it has a decimal, if so, we allow the resulting string to have four characters. You could also check for a decimal via

    n%1 == 0
    

    At the writing of this module I'm going through, I didn't actually think of that. Take your pick. The second option is probably more efficient than string checking. Anyway, that's what's going on (limiting the display string to the right number of characters). Lastly, we need a way to figure out what suffix fits our number. We know our number needs a suffix, we also know what power of ten our suffix should represent. Knowing this, we can think "Oh! Our suffix can be represented in a table with its index somehow correlated with the power of ten associated with the stored suffix." Turns out, we can use the array part of a table to do this for us, and, since the exponents are multiples of 3, we can divide by 3 to get our index:

    local list_of_suffixes = {
    	"K";
    	"M";
    	"B";
    	"T";
    	"qd";
    	"Qn";
    	"sx";
    	"Sp";
    	--# etc.
    }
    
    local amount_label_found = false
    local string_number
    
    for i = 3, x, 3 do
    
    	if number >= (10 ^ i) and number < (10 ^ (i + 3)) then
    	
    		amount_label_found = true --# we found our suffix area
    
    		local unchecked_m_variable = (math.floor(number / (10 ^ (i - 2))) / 100)	
    		local string_m_variable = tostring(unchecked_m_variable)
    		local decimal_check = string.find(string_m_variable, "%.")
    		local m_variable = string.sub(string_m_variable, 1, (decimal_check and decimal_check ~= 4) and 4 or 3)
    	
    		string_number = m_variable..list_of_suffixes[i/3]..(number > (unchecked_m_variable*100*10^(i - 2)) and "+" or "")
    		break --# no need to keep looking for a suffix, we found one
    	end
    end
    

    That last variable definition is a little complex looking. All it's doing is adding our suffix on, along with a + if there were numbers we took off during the truncating process so that the player knows their cash value is not exactly the number displayed on the gui/leaderboard (there might be a different/better way to check that, but I couldn't think of one at the time of the writing of the code).

    We're almost done. All we have to do is to see if the number couldn't find a suffix (i.e. it's likely less than 1,000). If not, then we just need to format it and then return that:

    local list_of_suffixes = {
    	"K";
    	"M";
    	"B";
    	"T";
    	"qd";
    	"Qn";
    	"sx";
    	"Sp";
    	--# etc.
    }
    
    local function transform_number(number)
    
    	local amount_label_found = false
    	local string_number
    
    	for i = 3, x, 3 do	
    
    
    		if number >= (10 ^ i) and number < (10 ^ (i + 3)) then
    	
    			amount_label_found = true --# we found our suffix area
    	
    			local unchecked_m_variable = (math.floor(number / (10 ^ (i - 2))) / 100)	
    			local string_m_variable = tostring(unchecked_m_variable)
    			local decimal_check = string.find(string_m_variable, "%.")
    			local m_variable = string.sub(string_m_variable, 1, (decimal_check and decimal_check ~= 4) and 4 or 3)
    		
    			string_number = m_variable..list_of_suffixes[i/3]..(number > (unchecked_m_variable*100*10^(i - 2)) and "+" or "")
    			break --# no need to keep looking for a suffix, we found one
    
    		end
    
    	end
    
    
    	if not amount_label_found then
    		string_number = tostring(math.floor(number*100 + 0.5)/100)
    	end
    
    	return string_number
    
    end
    

    With this system, you can add your own suffixes, and add however many you like. I didn't cover negative numbers, but it's as simple as checking if the number is less than 0, and, if so, get rid of the negative until you're done, and concatenate it onto the front at the end of the suffix addition process. That way it won't interfere with the number comparisons.

    I hope this helps, and good luck generalizing in the future!


  • In the writing of this tutorial, I am by no means claiming I have the best/most generalized way to do this. I'm sure there's better somewhere. I just want to help you learn how to generalize for yourself. Oh, also, I was messing around with the code for the tutorial, so it may be a little off. Ideally not, but, if so, please notify me so that I can adjust it.


  • Can't you just do string.len(amount) and then get the suffixes from that


  • @greatneil80, that adds complexity when there are decimals, and it doesn't work when numbers are written in scientific notation.


  • @Phlegethon5778 I see... It seems so simple to convert the scientific notation back to the amount though... Then do what I said.. I do understand the decimal part though, you are right..

Log in to reply
 

Looks like your connection to Scripting Helpers was lost, please wait while we try to reconnect.