Module:math: Difference between revisions

Jump to navigation Jump to search
no edit summary
(Created page with "local export = {} local format = string.format local is_integer -- defined as export.is_integer below local log = math.log local log10 = math.log10 local select = select local tonumber = tonumber local tostring = tostring local type = type --[==[ Returns true if the given value is a finite real number, or false if not.]==] function export.is_finite_real_number(n) return n and type(n) == "number" and n - n == 0 -- INF, -INF and NAN fail here. end --[==[ Returns true i...")
 
No edit summary
 
Line 1: Line 1:
local export = {}
local export = {}


local byte = string.byte
local ceil = math.ceil
local floor = math.floor
local format = string.format
local format = string.format
local is_integer -- defined as export.is_integer below
local is_integer -- defined below
local log = math.log
local match = string.match
local log10 = math.log10
local select = select
local select = select
local tonumber = tonumber
local tonumber = tonumber
local tonumber_ext -- defined below
local tostring = tostring
local tostring = tostring
local type = type
local type = type
local INF = math.huge
local function sign(x, signed_0)
if x > 0 then
return 1
elseif x < 0 then
return -1
elseif x == 0 then
-- 1/(+0) is infinity and 1/(-0) is -infinity.
return signed_0 and (1 / x > 0 and 1 or -1) or 0
end
-- NaN: convert to string with a forced sign prefix, and grab the first byte.
local sign = byte(format("%+f", x))
return sign == 0x2B and 1 or -- +
sign == 0x2D and -1 or -- -
-- If there's no sign, throw an error. This shouldn't be possible, but
-- avoids silent errors if it does happen.
error("Internal error: cannot determine sign of " .. x)
end


--[==[
--[==[
Returns true if the given value is a finite real number, or false if not.]==]
An extended version of {tonumber()}, which attempts to convert `x` to a number. Like {tonumber()}, it will convert from base 10 by default, and the optional parameter `base` can be used to specify a different base between 2 and 36, with the letters {A-Z} (case-insensitive) representing additional digits beyond {0-9}. When strings contain hexadecimal notation (e.g. {"0x100"}), base 16 is used as the default instead, but this is overridden if `base` is set to anything other than 16.
function export.is_finite_real_number(n)
 
return n and type(n) == "number" and n - n == 0 -- INF, -INF and NAN fail here.
This function differs from {tonumber()} in the following ways:
* If `finite_real` is set, then the function will only return finite real numbers; inputs which would normally produce ±infinity or NaN will instead produce {nil}.
* If `no_prefix` is set, then strings which start with {"0x"} will not be interpreted as containing hexadecimal notation, resulting in {nil}.
* If `base` is explicitly set to {10}, then strings in hexadecimal notation will always return {nil}. This fixes a bug in {tonumber()}, which treats {base=10} the same as {base} being unset, causing base 16 to be used if `x` contains hexadecimal notation (e.g. {tonumber("0x10", 10)} returns {16}, whereas {tonumber_extended("0x10", 10)} returns {nil}).]==]
function export.tonumber_extended(x, base, finite_real, no_prefix)
-- TODO: tonumber() maxes out at 2^64 if the base is anything other than 10.
-- TODO: support binary (0b) and octal (0o) prefixes.
local n = tonumber(x, base)
if not n or finite_real and (n ~= n or n == INF or n == -INF) then
return nil
-- If `base` is explicitly set to 10 (not simply nil), or `no_prefix` is set
-- and `base` is nil or 16, filter out inputs that started with hexadecimal
-- prefixes. Note that if `base` is anything else, the initial "0x" will
-- have been interpreted as digits by tonumber() instead of a prefix (as "x"
-- can be a digit from base 34 upwards), so there's no prefix to check for.
elseif base == 10 or no_prefix and (base == nil or base == 16) then
return not match(x, "^%s*[+-]?0[xX]()") and n or nil
end
return n
end
end
tonumber_ext = export.tonumber_extended


--[==[
--[==[
Returns true if the given value is an integer, or false if not.]==]
Converts `x` to an integer by removing the fractional portion (e.g. {3.5} becomes {3}, and {-2.9} becomes {-2}). This is equivalent to rounding down positive numbers and rounding up negative numbers. If conversion is not possible, returns {nil}.]==]
function export.is_integer(n)
function export.to_integer(x)
return n and type(n) == "number" and n % 1 == 0 -- INF, -INF and NAN also fail here.
x = tonumber(x)
if not (x and x == x and x ~= INF and x ~= -INF) then
return nil
elseif x % 1 == 0 then
return x
-- Round-down positives.
elseif x >= 0 then
return floor(x)
end
--Round-up negatives.
return ceil(x)
end
 
--[==[
Returns {1} if `x` is positive, {-1} if `x` is negative, or {0} if `x` is {0}.
 
If `signed_0` is set, this function will only return either {1} or {-1}, and will make a distinction between [[w:signed zero|signed zeroes]] ({+0} and {-0}). This is useful when a {0} result could be disruptive (e.g. {x % 0}).]==]
function export.sign(x, signed_0)
return sign(
tonumber(x) or
error(format("bad argument #1 to 'sign' (number expected, got %s)", type(x)), 2),
signed_0
)
end
 
--[==[
Returns {true} if `x` is a finite real number, or {false} if not.]==]
function export.is_finite_real_number(x)
return x and x == x and not (x == INF or x == -INF) and type(x) == "number"
end
 
--[==[
Returns {true} if `x` is an integer, or {false} if not.]==]
function export.is_integer(x)
return x and type(x) == "number" and x % 1 == 0 or false
end
end
is_integer = export.is_integer
is_integer = export.is_integer


--[==[
--[==[
Returns true if the given value is a positive integer (or zero if the `include_0` flag is set), or false if not.]==]
Returns {true} if `x` is a positive integer (or zero if the `include_0` flag is set), or {false} if not.]==]
function export.is_positive_integer(n, include_0)
function export.is_positive_integer(x, include_0)
return is_integer(n) and (n > 0 or include_0 and n == 0) or false
return x and type(x) == "number" and (x > 0 or include_0 and x == 0) and x % 1 == 0 or false
end
 
--[==[
Returns {true} is `x` is [[w:NaN|NaN]] (Not a Number), or {false} if not.
 
NaN is a value that has the type "number", but does not represent an actual numeric value; it has the unique property that if {x} is NaN, {x ~= x} evaluates to {true}.]==]
function export.is_NaN(x)
return x ~= x
end
end


Line 32: Line 116:
Returns the base-10 logarithm of `x`.
Returns the base-10 logarithm of `x`.


Should be used instead of `math.log10`, which is deprecated and may stop working
This function should be used instead of {math.log10}, which is deprecated and may stop working if Scribunto is updated to a more recent Lua version.]==]
if Scribunto is updated to a more recent Lua version.]==]
function export.log10(x) -- Structured like this so that module documentation works.
function export.log10(x)
local log10 = math.log10
if log10 == nil then
if log10 ~= nil then
if log(10, 10) == 1 then -- Lua 5.2
return log10
function log10(x)
end
return log(x, 10)
local log = math.log
end
return log(10, 10) == 1 and function(x) -- Lua 5.2
else
return log(x, 10)
function log10(x)
end or function(x) -- Lua 5.1
return log(x) * 0.43429448190325182765112891891660508229439700580367 -- log10(e)
return log(x) * 0.43429448190325182765112891891660508229439700580367 -- log10(e)
end
end
end
end
export.log10 = log10
end
end
export.log10() -- Sets the actual returned function. Structured like this so that module documentation works.
export.log10 = export.log10() -- Sets the actual returned function.


local function integer_error(x, param, func_name)
local function integer_error(x, param, func_name)
Line 59: Line 140:


--[==[
--[==[
Converts a decimal number to hexadecimal.]==]
Converts a decimal number to hexadecimal. If `include_prefix` is set, the returned number will include the 0x prefix.]==]
function export.to_hex(dec)
function export.to_hex(dec, include_prefix)
dec = tonumber(dec) or dec
dec = tonumber(dec) or dec
if not is_integer(dec, true) then
if not is_integer(dec) then
integer_error(dec, 1, "to_hex")
integer_error(dec, 1, "to_hex")
end
end
Line 70: Line 151:
end
end
-- Inputs >= 2^64 cause string.format to return "0".
-- Inputs >= 2^64 cause string.format to return "0".
if dec >= 18446744073709551616 then
if dec >= 0x1p64 then
error("integer overflow in 'to_hex': cannot convert inputs with a magnitude greater than or equal to 2^64 (18446744073709551616)", 2)
error("integer overflow in 'to_hex': cannot convert inputs with a magnitude greater than or equal to 2^64 (18446744073709551616)", 2)
end
end
-- string.format treats hex numbers as unsigned, so any sign must be added manually.
-- string.format treats hex numbers as unsigned, so any sign must be added manually.
return format("%s%X", neg and "-" or "", dec)
return format("%s%s%X", neg and "-" or "", include_prefix and "0x" or "", dec)
end
end


--[==[
--[==[
Returns the greatest common divisor.]==]
Returns the greatest common divisor of an arbitrary number of input numbers.]==]
function export.gcd(n, ...)
function export.gcd(x, ...)
n = tonumber(n) or n
x = tonumber(x) or x
if not is_integer(n) then
if not is_integer(x) then
integer_error(n, 1, "gcd")
integer_error(x, 1, "gcd")
end
end
local q, args_len, integers = ..., select("#", ...)
local q, args_len, integers = ..., select("#", ...)
Line 90: Line 171:
if not is_integer(q) then
if not is_integer(q) then
integer_error(q, i, "gcd")
integer_error(q, i, "gcd")
elseif n ~= 1 then -- If n is 1, validate remaining inputs.
elseif x ~= 1 then -- If x is 1, validate remaining inputs.
-- GCD of two integers n, q with Euclid's algorithm.
-- GCD of two integers x, q with Euclid's algorithm.
while q ~= 0 do
while q ~= 0 do
n, q = q, n % q
x, q = q, x % q
end
end
end
end
Line 104: Line 185:
end
end
end
end
return n < 0 and -n or n
return x < 0 and -x or x
end
end


--[==[
--[==[
Returns the least common multiple.]==]
Returns the least common multiple of an arbitrary number of input numbers.]==]
function export.lcm(n, ...)
function export.lcm(x, ...)
n = tonumber(n) or n
x = tonumber(x) or x
if not is_integer(n) then
if not is_integer(x) then
integer_error(n, 1, "lcm")
integer_error(x, 1, "lcm")
end
end
local q, args_len, integers = ..., select("#", ...)
local q, args_len, integers = ..., select("#", ...)
-- Compute the product of all inputs as p and GCD as n.
-- Compute the product of all inputs as p and GCD as x.
for i = 2, args_len + 1 do
for i = 2, args_len + 1 do
q = tonumber(q) or q
q = tonumber(q) or q
if not is_integer(q) then
if not is_integer(q) then
integer_error(q, i, "lcm")
integer_error(q, i, "lcm")
elseif n ~= 0 then  -- If n is 0, validate remaining inputs.
elseif x ~= 0 then  -- If x is 0, validate remaining inputs.
-- Compute the product.
-- Compute the product.
local p = n * q
local p = x * q
-- GCD of two integers n, q with Euclid's algorithm.
-- GCD of two integers x, q with Euclid's algorithm.
while q ~= 0 do
while q ~= 0 do
n, q = q, n % q
x, q = q, x % q
end
end
-- Divide product by the GCD to get new LCM.
-- Divide product by the GCD to get new LCM.
n = p / n
x = p / x
end
end
if i <= args_len then
if i <= args_len then
Line 138: Line 219:
end
end
end
end
return n < 0 and -n or n
return x < 0 and -x or x
end
end


return export
return export

Navigation menu