-- Round a finite float half away from zero, exactly. -- math.modf is exact, unlike the naive floor(x + 0.5) which goes wrong -- for values like 0.49999999999999994 (x + 0.5 rounds up to 1.0 in the float domain). local function roundHalfAwayFromZero(x) local i, f = math.modf(x) if f >= 0.5 then return i + 1 elseif f <= -0.5 then return i - 1 end return i end --- Round a number to the nearest integer, or to N decimal places. --- Rounds half away from zero. round(value, 0) returns an integer; round(value, places > 0) returns a float. function math.round(value, places) if type(value) ~= "number" then error("round() called with a non-number value") end if places == nil then places = 0 else places = math.tointeger(places) if places == nil then error("round() places must be an integer number") end if places < 0 then error("round() called with negative places") end end if math.type(value) == "integer" then if places == 0 then return value end return value + 0.0 end if not math.isFinite(value) then error("round() called with a non-finite value (NaN or infinity)") end if places == 0 then local int = math.tointeger(roundHalfAwayFromZero(value)) if int == nil then error("round() called with value out of range of Lua integer") end return int end local mul = 10.0 ^ places local scaled = value * mul if not math.isFinite(scaled) then return value end return roundHalfAwayFromZero(scaled) / mul end --- Clamp a number to an interval [min, max]. Either bound may be nil (half-open). function math.clamp(value, min, max) if type(value) ~= "number" then error("Non-number passed to clamp() as value") end if min ~= nil and type(min) ~= "number" then error("Non-number passed to clamp() as minimum") end if max ~= nil and type(max) ~= "number" then error("Non-number passed to clamp() as maximum") end if min ~= nil and max ~= nil and min > max then error("clamp() minimum is greater than maximum") end if min ~= nil and value < min then return min end if max ~= nil and value > max then return max end return value end --- Return true if value is a finite number (not NaN or infinity). function math.isFinite(value) if type(value) ~= "number" then return false end return value == value and value ~= math.huge and value ~= -math.huge end --- Return the sign of a number: -1, 0, or 1. NaN yields 0. function math.sign(value) if type(value) ~= "number" then error("Non-number passed to sign()") end if value > 0 then return 1 elseif value < 0 then return -1 else return 0 end end --- Scale a value linearly from [inMin, inMax] to [outMin, outMax]. --- If clamp is true, the result is clamped to the output range. function math.scale(value, inMin, inMax, outMin, outMax, clamp) if type(value) ~= "number" then error("Non-number passed to scale() as value") end if type(inMin) ~= "number" or type(inMax) ~= "number" then error("Non-number passed to scale() as input range") end if type(outMin) ~= "number" or type(outMax) ~= "number" then error("Non-number passed to scale() as output range") end if inMin == inMax then error("Input range cannot be zero (inMin == inMax)") end local ratio = (value - inMin) / (inMax - inMin) local result = outMin + ratio * (outMax - outMin) if clamp then local lo, hi = outMin, outMax if lo > hi then lo, hi = hi, lo end if result < lo then return lo elseif result > hi then return hi end end return result end