TradeSkillMaster/LibTSM/Util/Math.lua

163 lines
5.3 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- Math Functions
-- @module Math
local _, TSM = ...
local Math = TSM.Init("Util.Math")
local TempTable = TSM.Include("Util.TempTable")
local NAN = math.huge * 0
local IS_NAN_GT_INF = NAN > math.huge
local NAN_STR = tostring(NAN)
local private = {
keysTemp = {},
}
-- ============================================================================
-- Module Functions
-- ============================================================================
--- Returns NAN.
-- @treturn number NAN
function Math.GetNan()
return NAN
end
--- Checks if a value is NAN.
-- @tparam number value The number to check
-- @treturn boolean Whether or not the value is NAN
function Math.IsNan(value)
if IS_NAN_GT_INF then
-- optimization if NAN > math.huge (which it is in Wow's version of lua)
return value > math.huge and tostring(value) == NAN_STR
else
return tostring(value) == NAN_STR
end
end
--- Rounds a value to a specified significant value.
-- @tparam number value The number to be rounded
-- @tparam number sig The value to round to the nearest multiple of
-- @treturn number The rounded value
function Math.Round(value, sig)
sig = sig or 1
return floor((value / sig) + 0.5) * sig
end
--- Rounds a value down to a specified significant value.
-- @tparam number value The number to be rounded
-- @tparam number sig The value to round down to the nearest multiple of
-- @treturn number The rounded value
function Math.Floor(value, sig)
sig = sig or 1
return floor(value / sig) * sig
end
--- Rounds a value up to a specified significant value.
-- @tparam number value The number to be rounded
-- @tparam number sig The value to round up to the nearest multiple of
-- @treturn number The rounded value
function Math.Ceil(value, sig)
sig = sig or 1
return ceil(value / sig) * sig
end
--- Scales a value from one range to another.
-- @tparam number value The number to be scaled
-- @tparam number fromStart The start value of the range to scale from
-- @tparam number fromEnd The end value of the range to scale from (can be less than fromStart)
-- @tparam number toStart The start value of the range to scale to
-- @tparam number toEnd The end value of the range to scale to (can be less than toStart)
-- @treturn number The scaled value
function Math.Scale(value, fromStart, fromEnd, toStart, toEnd)
assert(value >= min(fromStart, fromEnd) and value <= max(fromStart, fromEnd))
return toStart + ((value - fromStart) / (fromEnd - fromStart)) * (toEnd - toStart)
end
--- Bounds a number between a min and max value.
-- @tparam number value The number to be bounded
-- @tparam number minValue The min value
-- @tparam number maxValue The max value
-- @treturn number The bounded value
function Math.Bound(value, minValue, maxValue)
return min(max(value, minValue), maxValue)
end
--- Calculates the has of the specified data
-- This data can handle data of type string or number. It can also handle a table being passed as the data assuming
-- all keys and values of the table are also hashable (strings, numbers, or tables with the same restriction). This
-- function uses the [djb2 algorithm](http://www.cse.yorku.ca/~oz/hash.html).
-- @param data The data to be hased
-- @tparam[opt] number hash The initial value of the hash
-- @treturn number The hash value
function Math.CalculateHash(data, hash)
hash = hash or 5381
local maxValue = 2 ^ 24
local dataType = type(data)
if dataType == "string" then
-- iterate through 8 bytes at a time
for i = 1, ceil(#data / 8) do
local b1, b2, b3, b4, b5, b6, b7, b8 = strbyte(data, (i - 1) * 8 + 1, i * 8)
hash = (hash * 33 + b1) % maxValue
if not b2 then break end
hash = (hash * 33 + b2) % maxValue
if not b3 then break end
hash = (hash * 33 + b3) % maxValue
if not b4 then break end
hash = (hash * 33 + b4) % maxValue
if not b5 then break end
hash = (hash * 33 + b5) % maxValue
if not b6 then break end
hash = (hash * 33 + b6) % maxValue
if not b7 then break end
hash = (hash * 33 + b7) % maxValue
if not b8 then break end
hash = (hash * 33 + b8) % maxValue
end
elseif dataType == "number" then
assert(data == floor(data), "Invalid number")
if data < 0 then
data = data * -1
hash = (hash * 33 + 59) % maxValue
end
while data > 0 do
hash = (hash * 33 + data % 256) % maxValue
data = floor(data / 256)
end
elseif dataType == "table" then
local keys = nil
if private.keysTemp.inUse then
keys = TempTable.Acquire()
else
keys = private.keysTemp
private.keysTemp.inUse = true
end
for k in pairs(data) do
tinsert(keys, k)
end
sort(keys)
for _, key in ipairs(keys) do
hash = Math.CalculateHash(key, hash)
hash = Math.CalculateHash(data[key], hash)
end
if keys == private.keysTemp then
wipe(private.keysTemp)
else
TempTable.Release(keys)
end
elseif dataType == "boolean" then
hash = (hash * 33 + (data and 1 or 0)) % maxValue
elseif dataType == "nil" then
hash = (hash * 33 + 17) % maxValue
else
error("Invalid data")
end
return hash
end