48,355
edits
(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 | local is_integer -- defined below | ||
local | local match = string.match | ||
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 | |||
--[==[ | --[==[ | ||
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. | |||
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 | 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( | function export.to_integer(x) | ||
return | 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 | 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( | function export.is_positive_integer(x, include_0) | ||
return | 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`. | ||
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 | if log10 ~= nil then | ||
return log10 | |||
end | |||
local log = math.log | |||
return log(10, 10) == 1 and function(x) -- Lua 5.2 | |||
return log(x, 10) | |||
end or function(x) -- Lua 5.1 | |||
return log(x) * 0.43429448190325182765112891891660508229439700580367 -- log10(e) | |||
end | end | ||
end | end | ||
export.log10() -- Sets the actual returned function | 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 | 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 >= | 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( | function export.gcd(x, ...) | ||
x = tonumber(x) or x | |||
if not is_integer( | if not is_integer(x) then | ||
integer_error( | 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 | elseif x ~= 1 then -- If x is 1, validate remaining inputs. | ||
-- GCD of two integers | -- GCD of two integers x, q with Euclid's algorithm. | ||
while q ~= 0 do | while q ~= 0 do | ||
x, q = q, x % q | |||
end | end | ||
end | end | ||
| Line 104: | Line 185: | ||
end | end | ||
end | end | ||
return | 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( | function export.lcm(x, ...) | ||
x = tonumber(x) or x | |||
if not is_integer( | if not is_integer(x) then | ||
integer_error( | 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 | -- 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 | elseif x ~= 0 then -- If x is 0, validate remaining inputs. | ||
-- Compute the product. | -- Compute the product. | ||
local p = | local p = x * q | ||
-- GCD of two integers | -- GCD of two integers x, q with Euclid's algorithm. | ||
while q ~= 0 do | while q ~= 0 do | ||
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. | ||
x = p / x | |||
end | end | ||
if i <= args_len then | if i <= args_len then | ||
| Line 138: | Line 219: | ||
end | end | ||
end | end | ||
return | return x < 0 and -x or x | ||
end | end | ||
return export | return export | ||