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.
115 lines
3.7 KiB
115 lines
3.7 KiB
-- 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
|
|
|