[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Lua
. . -> [Subject]  Rounding, duration, comma functions

Rounding, duration, comma functions

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Sat 14 Apr 2007 11:56 PM (UTC)

Amended on Sun 15 Apr 2007 04:52 AM (UTC) by Nick Gammon

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 half-way. 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 60-second 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 left-over 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
[Go to top] top

Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Sat 19 Apr 2008 09:20 PM (UTC)

Amended on Sat 19 Apr 2008 09:21 PM (UTC) by Nick Gammon

Message
I have made the above functions into a module, which is easier to include in multiple projects:

File: commas.lua


-- commas.lua
-- ----------------------------------------------------------
-- Rounding, duration, comma functions
-- See forum thread:
--  http://www.gammon.com.au/forum/?id=7805

--[[

This function rounds any number to the closest integer.
The "tricky" case is exactly half-way. 
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

--[[

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"


--]]

-- ----------------------------------------------------------

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 

--[[

Commas in numbers

This function adds commas to big numbers. 
For example 123456 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 left-over digits, the comma part,
  -- and the stuff after the decimal point, if any
  return sign .. before .. result .. after

end -- function commas



Simply copy between the lines, and save as "commas.lua" in the "lua" subdirectory of your MUSHclient installation.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Fiendish   USA  (2,514 posts)  [Biography] bio   Global Moderator
Date Reply #2 on Mon 22 Apr 2013 05:20 AM (UTC)

Amended on Mon 22 Apr 2013 05:21 AM (UTC) by Fiendish

Message
Nick mentions Banker's Rounding above. Oddly enough, I recently found myself in need of this very thing, despite the claimed probable insignificance.

I implemented it like this


-- round normally, but when a number ends in exactly .5 round to the nearest even value.
function round_banker(x)
   if x == 0 then return 0 end -- prevent returning -0
   if (x + 0.5) % 2 == 0 then
      return math.floor(x + 0.5)
   else
      return math.ceil(x - 0.5)
   end
end

https://github.com/fiendish/aardwolfclientpackage
[Go to top] top

Posted by Ashleykitsune   (33 posts)  [Biography] bio
Date Reply #3 on Sun 30 Mar 2014 06:26 AM (UTC)

Amended on Tue 01 Apr 2014 01:16 PM (UTC) by Ashleykitsune

Message
I have two questions in regards to the convert_time function.

First, the function checks in order of weeks, days, hours, minutes, then seconds - I was wondering, is there any advantage in doing it in this order, rather than the other way around? It makes more sense to me to do it in reverse, at least in my experience, because I don't tend to see higher numbers being used too often. But that's just my experience.

such as: (unsure how accurate the below code is, I just manipulated it based on what was in the thread. I'm at work so I can't check it. I am still fairly new to this so I'm assuming that once "return" is called the function is completed, and stops processing.)


function convert_time (secs)

  -- handle negative numbers
  local sign = ""
  if secs < 0 then
    secs = math.abs (secs)
    sign = "-"
  end -- if negative seconds
 
  -- seconds
  if secs <= 59.5 then
   return sign .. round (secs) .. " s"   
  end -- 59.5 or more seconds

  -- minutes
  if secs <= (60 * 59.5) then
   return sign .. round (secs / 60) .. " m"
  end -- 59.5 or more minutes
  
  -- hours
  if secs <= (60 * 60 * 23.5) then
   return sign .. round (secs / (60 * 60)) .. " h"
  end -- 23.5 or more hours

  -- days
  if secs <= (60 * 60 * 24 * 6.5) then
    return sign .. round (secs / (60 * 60 * 24)) .. " d"
  end -- 6.5 or more days

  -- weeks
    return sign .. round (secs / (60 * 60 * 24 * 7)) .. " w"
end -- function convert_time


Also, I was thinking it would be beneficial to add a second optional argument "round" that would default to true if not specified (so that it continues to work as normal) if added, where if false it returns a decimal value (I would hope rounded to the tenth). To me, seeing 5.6 h would be more valuable than just 6 h.

for instance: (And again this is just something I threw together, I don't know if it is accurate)

function convert_time (secs, round)  -- round = optional boolean to return decimal value, default is true

  if round == nil then round = true

  -- handle negative numbers
  local sign = ""
  if secs < 0 then
    secs = math.abs (secs)
    sign = "-"
  end -- if negative seconds


if round then  -- for a rounded value
    -- seconds
    if secs <= 59.5 then
        return sign .. round (secs) .. " s"
    end -- 59.5 or more seconds

    -- minutes
    if secs <= (60 * 59.5) then
        return sign .. round (secs / 60) .. " m"
    end -- 59.5 or more minutes
  
    -- hours
    if secs <= (60 * 60 * 23.5) then
     return sign .. round (secs / (60 * 60)) .. " h"
    end -- 23.5 or more hours

    -- days
    if secs <= (60 * 60 * 24 * 6.5) then
      return sign .. round (secs / (60 * 60 * 24)) .. " d"
    end -- 6.5 or more days

    -- weeks
      return sign .. round (secs / (60 * 60 * 24 * 7)) .. " w"
    end -- round - function convert_time

end -- if round == true (default)

-- if not round == true we'll return the decimal value


  -- seconds
  if secs <= 59.5 then
      return sign .. secs .. " s"
  end -- 59.5 or more seconds

  -- minutes
  if secs <= (60 * 59.5) then
      return sign .. (secs / 60) .. " m"
  end -- 59.5 or more minutes
  
  -- hours
  if secs <= (60 * 60 * 23.5) then
   return sign .. (secs / (60 * 60)) .. " h"
  end -- 23.5 or more hours

  -- days
  if secs <= (60 * 60 * 24 * 6.5) then
    return sign .. (secs / (60 * 60 * 24)) .. " d"
  end -- 6.5 or more days

  -- weeks
    return sign .. (secs / (60 * 60 * 24 * 7)) .. " w"
end -- function convert_time


Thanks for your time,
[Go to top] top

Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Reply #4 on Mon 31 Mar 2014 10:00 PM (UTC)
Message
I can't see any major objection to reversing the order, offhand.

As for the rounding, wouldn't you usually want to round? Otherwise you might get borderline cases (eg. 59.9 seconds) begin shown as 0 m.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Ashleykitsune   (33 posts)  [Biography] bio
Date Reply #5 on Tue 01 Apr 2014 04:30 AM (UTC)

Amended on Wed 02 Apr 2014 03:06 AM (UTC) by Ashleykitsune

Message
Nick Gammon said:

I can't see any major objection to reversing the order, offhand.

As for the rounding, wouldn't you usually want to round? Otherwise you might get borderline cases (eg. 59.9 seconds) begin shown as 0 m.


Good point, do you think that changing it to the following would help to fix that? I'm not sure if the decimal value is cut at the tenths place, ones place, or even smaller than that so I'd be afraid (since I still haven't tested it) that it would return a crazy number that doesn't fit what I want.


--last half of above function

-- if not round == true we'll return the decimal value

  -- seconds
  if secs < 60.0 then
      return sign .. secs .. " s"
  end -- 59.9 or less seconds

  -- minutes
  if secs < (60 * 60) then
      return sign .. (secs / 60) .. " m"
  end -- 59.9 or less minutes
  
  -- hours
  if secs < (60 * 60 * 24) then
   return sign .. (secs / (60 * 60)) .. " h"
  end -- 23.9 or less hours

  -- days
  if secs < (60 * 60 * 24 * 7) then
    return sign .. (secs / (60 * 60 * 24)) .. " d"
  end -- 6.9 or less days

  -- weeks
    return sign .. (secs / (60 * 60 * 24 * 7)) .. " w"
end -- function convert_time
[Go to top] top

Posted by Nick Gammon   Australia  (22,985 posts)  [Biography] bio   Forum Administrator
Date Reply #6 on Tue 01 Apr 2014 07:57 PM (UTC)
Message
That could be OK, although you have one "<=" in there and the rest "<".

:)

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Ashleykitsune   (33 posts)  [Biography] bio
Date Reply #7 on Wed 02 Apr 2014 08:29 AM (UTC)
Message
Nick Gammon said:

That could be OK, although you have one "<=" in there and the rest "<".

:)


Ah thanks, not sure how I missed that! I fixed it in the previous message.

Now I need a reason to use it!
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


36,293 views.

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]