You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
5.4 KiB

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...) ---@type TSM
local Math = TSM.Init("Util.Math") ---@class Util.Math
local TempTable = TSM.Include("Util.TempTable")
local NAN = math.huge * 0
local IS_NAN_GT_INF = (NAN or 0) > math.huge
local NAN_STR = tostring(NAN)
local private = {
keysTemp = {},
}
-- ============================================================================
-- Module Functions
-- ============================================================================
---Returns NAN.
---@return number @NAN
function Math.GetNan()
assert(NAN)
return NAN
end
---Checks if a value is NAN.
---@param value number The number to check
---@return boolean @Whether or not the value is NAN
function Math.IsNan(value)
if not NAN then
error("NAN not set")
end
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.
---@param value number The number to be rounded
---@param sig? number The value to round to the nearest multiple of (defaults to 1)
---@return 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.
---@param value number The number to be rounded
---@param sig? number The value to round down to the nearest multiple of (defaults to 1)
---@return 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.
---@param value number The number to be rounded
---@param sig? number The value to round up to the nearest multiple of (defaults to 1)
---@return 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.
---@param value number The number to be scaled
---@param fromStart number The start value of the range to scale from
---@param fromEnd number The end value of the range to scale from (can be less than fromStart)
---@param toStart number The start value of the range to scale to
---@param toEnd number The end value of the range to scale to (can be less than toStart)
---@return 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.
---@param value number The number to be bounded
---@param minValue number The min value
---@param maxValue number The max value
---@return number @The bounded value
function Math.Bound(value, minValue, maxValue)
return min(max(value, minValue), maxValue)
end
---Calculates the hash 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 any The data to be hased
---@param hash? number hash The initial value of the hash
---@return 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