Posted by
 Nick Gammon
Australia (23,006 posts) bio
Forum Administrator 
Message
 Here are three handy functions I have been working on recently:
Round
Need to round a number? This function rounds any number to the closest integer. The "tricky" case is exactly halfway. That is, should 1.5 round to 1 or 2? How about 1.5?
This function rounds 1.5 "up" to 2, and 1.5 "down" to 2.
 round "up" to absolute value, so we treat negative differently
 that is, round (1.5) will return 2
function round (x)
if x >= 0 then
return math.floor (x + 0.5)
end  if positive
return math.ceil (x  0.5)
end  function round
Rather fascinatingly, there are lots of ways of calculating rounding. See this article, for example:
http://en.wikipedia.org/wiki/Rounding
The method implemented above is the Symmetric Arithmetic Rounding algorithm. It has the slight flaw that it always rounds upwards on exactly x.5, whereas you could argue that it is equally valid to round down.
Other, more complicated methods, like Bankers' Rounding, round sometimes up and sometimes down, in order to even out the slight bias that rounding up gives. However for applications like computer games, the difference is probably not significant.
Duration
This function is designed to display a time interval in "short form". That is, rounded to the nearest major time interval. Some examples of intervals:
 3.6 days  displays "4 d"
 3.5 days  displays "4 d"
 3.4 days  displays "3 d"
 3.6 hours  displays "4 h"
 3.5 hours  displays "4 h"
 3.4 hours  displays "3 h"
 3.6 minutes  displays "4 m"
 3.5 minutes  displays "4 m"
 3.4 minutes  displays "3 m"
 59 seconds  displays "59 s"
 58 seconds  displays "58 s"
 57 seconds  displays "57 s" ... and so on to "0 s"
The intention here is to keep track of something, like how long you estimate it will take to level up, or how long a spell will take to wear off, or the time till the next "tick".
If you estimate that you will level in 4+ hours, then you don't really care about the number of seconds. In other words, something like this is too much information: "4h 15m 32s". This is especially true if the number is an estimate.
function convert_time (secs)
 handle negative numbers
local sign = ""
if secs < 0 then
secs = math.abs (secs)
sign = ""
end  if negative seconds
 weeks
if secs >= (60 * 60 * 24 * 6.5) then
return sign .. round (secs / (60 * 60 * 24 * 7)) .. " w"
end  6.5 or more days
 days
if secs >= (60 * 60 * 23.5) then
return sign .. round (secs / (60 * 60 * 24)) .. " d"
end  23.5 or more hours
 hours
if secs >= (60 * 59.5) then
return sign .. round (secs / (60 * 60)) .. " h"
end  59.5 or more minutes
 minutes
if secs >= 59.5 then
return sign .. round (secs / 60) .. " m"
end  59.5 or more seconds
 seconds
return sign .. round (secs) .. " s"
end  function convert_time
Note that this function requires the "round" function mentioned above.
The reason for the transition points not being exactly on the day/hour/minute etc. is because of rounding. If we switched to showing seconds (rather than minutes) at exactly the 60second point, then 59.9 seconds would be rounded up to "60 s". However 60 seconds should be displayd as "1 m", not "60 s". Thus, we transition at the point at which rounding will stop happening.
Commas in numbers
This function adds commas to big numbers. For example 123456 xp becomes "123,456".
function commas (num)
assert (type (num) == "number" or
type (num) == "string")
local result = ""
 split number into 3 parts, eg. 1234.545e22
 sign = + or 
 before = 1234
 after = .545e22
local sign, before, after =
string.match (tostring (num), "^([%+%]?)(%d*)(%.?.*)$")
 pull out batches of 3 digits from the end, put a comma before them
while string.len (before) > 3 do
result = "," .. string.sub (before, 3, 1) .. result
before = string.sub (before, 1, 4)  remove last 3 digits
end  while
 we want the original sign, any leftover digits, the comma part,
 and the stuff after the decimal point, if any
return sign .. before .. result .. after
end  function commas

 Nick Gammon
www.gammon.com.au, www.mushclient.com  top 
