Module:Number: Difference between revisions
From Melvor Idle
m (Add formatNum from Module:Shared) |
No edit summary |
||
(12 intermediate revisions by the same user not shown) | |||
Line 16: | Line 16: | ||
d = 1e30 | d = 1e30 | ||
} | } | ||
-- | local function sigfig(x, y) | ||
local x_sign = x < 0 and -1 or 1 | |||
local x = math.abs(x) | |||
local n = math.floor(math.log10(x)) + 1 - y | |||
return x_sign * math.pow(10, n) * p.round2(x / math.pow(10, n), 0) | |||
end | |||
-- Automatically rounds to 2 places from the significant figure. | |||
-- Taken from RSWiki. | |||
function p.autoround(x) | |||
x = tonumber(x) or 0 | |||
local _x | |||
if x == 0 then | |||
_x = 0 | |||
elseif math.abs(x) < 0.1 then | |||
_x = sigfig(x, 2) | |||
elseif math.abs(x) > 999 then | |||
_x = p.round2(x, 0) | |||
else | |||
_x = p.round2(x, 2) | |||
end | |||
return _x | |||
end | |||
--- Formats a number by inserting commas as thousand separators. | |||
-- @param number (number or string) The number to format. | |||
-- @return (string) The formatted number with commas as thousand separators | |||
function p.formatnum(number) | function p.formatnum(number) | ||
if tonumber(number) == nil then | if tonumber(number) == nil then | ||
return number | return number | ||
end | |||
-- Find out of the number is using scientific notation. | |||
-- If it is, convert it to a string and remove the trailing zeroes. | |||
local result = tostring(number) | |||
if result:find("[eE]") ~= nil then | |||
result = string.format("%.20f", number) | |||
result = result:gsub("%.?0*$", "") | |||
end | |||
while true do | |||
-- Format in blocks of 3 digits at a time until formatting is complete | |||
local k | |||
result, k = string.gsub(result, "^(-?%d+)(%d%d%d)", '%1,%2') | |||
if k == 0 then | |||
break | |||
end | |||
end | |||
return result | |||
end | |||
--- Rounds a number to a specified number of digits after the decimal point. | |||
-- @param val (number or string) The value to round. | |||
-- @param maxDigits (number) [optional] The maximum number of digits after the decimal point. | |||
-- @param minDigits (number) [optional] The minimum number of digits after the decimal point. | |||
-- @return (string) The rounded number with the specified number of digits after the decimal point, or the input unchanged if it's not a number. | |||
function p.round(val, maxDigits, minDigits) | |||
if val == nil then | |||
return nil | |||
else | else | ||
if type(maxDigits) == "table" then | |||
minDigits = maxDigits[2] | |||
maxDigits = maxDigits[1] | |||
end | end | ||
local result = val.."" | |||
local decimals = string.find(result, "%.") | |||
if decimals ~= nil then | |||
decimals = string.len(result) - decimals | |||
else | |||
decimals = 0 | |||
end | |||
if maxDigits ~= nil and decimals > maxDigits then | |||
result = string.format("%."..maxDigits.."f", result) | |||
elseif minDigits ~= nil and decimals < minDigits then | |||
result = string.format("%."..minDigits.."f", result) | |||
end | |||
return result | return result | ||
end | end | ||
end | end | ||
--- Rounds a number to a specified number of decimal places. | |||
-- @param num (number) The number to round. | |||
-- @param numDecimalPlaces (number) [optional] The number of decimal places to round to. | |||
-- @return (number) The rounded number with the specified number of decimal places, or the nearest integer if numDecimalPlaces is not provided. | |||
--From http://lua-users.org/wiki/SimpleRound | |||
function p.round2(num, numDecimalPlaces) | |||
local mult = 10^(numDecimalPlaces or 0) | |||
return math.floor(num * mult + 0.5) / mult | |||
end | |||
-- Euclidean Greatest Common Divisor algorithm | |||
function p.gcd(a, b) | |||
if b ~= 0 then | |||
return p.gcd(b, a % b) | |||
else | |||
return math.abs(a) | |||
end | |||
end | |||
--Formats a pair of numbers as a reduced fraction | |||
function p.fraction(n, d) | |||
local gcd = p.gcd(n, d) | |||
return p.formatnum(n/gcd)..'/'..p.formatnum(d/gcd) | |||
end | |||
--Similar to p.fraction but returns the simplified numerator and denomerator separately without formatting | |||
function p.fractionpair(n, d) | |||
local gcd = p.gcd(n, d) | |||
return n / gcd, d / gcd | |||
end | |||
--Returns a number including the sign, even if positive | |||
function p.numStrWithSign(number) | |||
if number >= 0 then | |||
return '+'..p.formatnum(number) | |||
else | |||
return p.formatnum(number) | |||
end | |||
end | |||
--- Clamps a value between a minimum and maximum range. | |||
-- @param value (number) The value to be clamped. | |||
-- @param min (number) The minimum value in the range. | |||
-- @param max (number) The maximum value in the range. | |||
-- @return (number) The clamped value within the specified range. | |||
function p.clamp(value, min, max) | |||
return math.min(math.max(value, min), max) | |||
end | |||
--- Parses a string representation of a number with suffixes | --- Parses a string representation of a number with suffixes | ||
Line 85: | Line 199: | ||
end | end | ||
-- Converts a string to a | --- Converts a string to a number or returns a default value if conversion fails. | ||
-- string | -- @param str (string) The string to convert to a number. | ||
-- @param def (number) The default value to return if conversion fails or the input is nil. | |||
-- @return (number) The numeric value represented by the input string or the default value if conversion fails. | |||
function p.toNumberOrDefault(str, def) | function p.toNumberOrDefault(str, def) | ||
local num = tonumber(str) | local num = tonumber(str) | ||
Line 96: | Line 212: | ||
end | end | ||
-- | --- Converts a string to a number or throws an error with a specified message if conversion fails. | ||
-- | -- @param str (string) The string to convert to a number. | ||
-- @param errorMsg (string) [optional] The error message to throw if conversion fails. Defaults to "NaN". | |||
-- @return (number) The numeric value represented by the input string. | |||
-- @throws Error with specified error message if conversion fails. | |||
function p.toNumberOrError(str, errorMsg) | function p.toNumberOrError(str, errorMsg) | ||
local num = tonumber(str) | local num = tonumber(str) | ||
Line 111: | Line 230: | ||
error(msg) | error(msg) | ||
end | end | ||
end | |||
-- Returns the probability of getting at least one drop, provided | |||
-- the drop rate and the current amount of drops. | |||
function p.getDropProbability(dropRate, totalDrops) | |||
dropRate = p.clamp(dropRate, 0, 1) | |||
return 1 - (1- dropRate) ^ 500 | |||
end | |||
-- Returns the amount of drops required for a certain drop probability. | |||
function p.getDropsForProbability(dropRate, probability) | |||
dropRate = p.clamp(dropRate, 0, 1) | |||
probability = p.clamp(probability, 0, 1) | |||
return math.log(1 - probability) / math.log(1 - dropRate) | |||
end | end | ||
return p | return p |
Latest revision as of 19:19, 17 April 2024
Documentation for this module may be created at Module:Number/doc
--
-- Module that contains functions related to numeric formatting or number manipulation
--
local p = {}
local numberSuffixes = {
k = 1e3,
m = 1e6,
b = 1e9,
t = 1e12,
q = 1e15,
s = 1e18,
o = 1e21,
n = 1e24,
d = 1e30
}
local function sigfig(x, y)
local x_sign = x < 0 and -1 or 1
local x = math.abs(x)
local n = math.floor(math.log10(x)) + 1 - y
return x_sign * math.pow(10, n) * p.round2(x / math.pow(10, n), 0)
end
-- Automatically rounds to 2 places from the significant figure.
-- Taken from RSWiki.
function p.autoround(x)
x = tonumber(x) or 0
local _x
if x == 0 then
_x = 0
elseif math.abs(x) < 0.1 then
_x = sigfig(x, 2)
elseif math.abs(x) > 999 then
_x = p.round2(x, 0)
else
_x = p.round2(x, 2)
end
return _x
end
--- Formats a number by inserting commas as thousand separators.
-- @param number (number or string) The number to format.
-- @return (string) The formatted number with commas as thousand separators
function p.formatnum(number)
if tonumber(number) == nil then
return number
end
-- Find out of the number is using scientific notation.
-- If it is, convert it to a string and remove the trailing zeroes.
local result = tostring(number)
if result:find("[eE]") ~= nil then
result = string.format("%.20f", number)
result = result:gsub("%.?0*$", "")
end
while true do
-- Format in blocks of 3 digits at a time until formatting is complete
local k
result, k = string.gsub(result, "^(-?%d+)(%d%d%d)", '%1,%2')
if k == 0 then
break
end
end
return result
end
--- Rounds a number to a specified number of digits after the decimal point.
-- @param val (number or string) The value to round.
-- @param maxDigits (number) [optional] The maximum number of digits after the decimal point.
-- @param minDigits (number) [optional] The minimum number of digits after the decimal point.
-- @return (string) The rounded number with the specified number of digits after the decimal point, or the input unchanged if it's not a number.
function p.round(val, maxDigits, minDigits)
if val == nil then
return nil
else
if type(maxDigits) == "table" then
minDigits = maxDigits[2]
maxDigits = maxDigits[1]
end
local result = val..""
local decimals = string.find(result, "%.")
if decimals ~= nil then
decimals = string.len(result) - decimals
else
decimals = 0
end
if maxDigits ~= nil and decimals > maxDigits then
result = string.format("%."..maxDigits.."f", result)
elseif minDigits ~= nil and decimals < minDigits then
result = string.format("%."..minDigits.."f", result)
end
return result
end
end
--- Rounds a number to a specified number of decimal places.
-- @param num (number) The number to round.
-- @param numDecimalPlaces (number) [optional] The number of decimal places to round to.
-- @return (number) The rounded number with the specified number of decimal places, or the nearest integer if numDecimalPlaces is not provided.
--From http://lua-users.org/wiki/SimpleRound
function p.round2(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
-- Euclidean Greatest Common Divisor algorithm
function p.gcd(a, b)
if b ~= 0 then
return p.gcd(b, a % b)
else
return math.abs(a)
end
end
--Formats a pair of numbers as a reduced fraction
function p.fraction(n, d)
local gcd = p.gcd(n, d)
return p.formatnum(n/gcd)..'/'..p.formatnum(d/gcd)
end
--Similar to p.fraction but returns the simplified numerator and denomerator separately without formatting
function p.fractionpair(n, d)
local gcd = p.gcd(n, d)
return n / gcd, d / gcd
end
--Returns a number including the sign, even if positive
function p.numStrWithSign(number)
if number >= 0 then
return '+'..p.formatnum(number)
else
return p.formatnum(number)
end
end
--- Clamps a value between a minimum and maximum range.
-- @param value (number) The value to be clamped.
-- @param min (number) The minimum value in the range.
-- @param max (number) The maximum value in the range.
-- @return (number) The clamped value within the specified range.
function p.clamp(value, min, max)
return math.min(math.max(value, min), max)
end
--- Parses a string representation of a number with suffixes
-- @param str (string) The string to parse.
-- @return (number) The numeric value represented by the input string or 0 if the string is NaN.
function p.parseNumber(str)
-- Empty/nil string.
if not str or str == "" then
return 0
end
-- If the input is already a number, return it.
local number = tonumber(str)
if number then
return number
end
local numStr = str:sub(1, -2)
local suffix = str:sub(-1):lower()
if tonumber(numStr) then
local multiplier = numberSuffixes[suffix] or 1
return numStr * multiplier
else
return 0
end
end
--- Format a number with optional suffixes based on its magnitude.
-- @param number (number) The number to be formatted.
-- @param decimals (number) [optional] The number of decimals to include in the formatted output. Default is 2.
-- @return (string) The formatted number as a string
function p.abbreviateNumber(number, decimals)
if type(number) ~= "number" then
error("NaN")
end
-- Round to two decimals by default.
decimals = decimals or 2
for suffix, value in pairs(numberSuffixes) do
if number >= value then
if number % 1 ~= 0 then
return string.format("%." .. decimals .. "f%s", number / value, suffix)
else
return string.format("%.0f%s", number / value, suffix)
end
end
end
return tostring(number)
end
--- Converts a string to a number or returns a default value if conversion fails.
-- @param str (string) The string to convert to a number.
-- @param def (number) The default value to return if conversion fails or the input is nil.
-- @return (number) The numeric value represented by the input string or the default value if conversion fails.
function p.toNumberOrDefault(str, def)
local num = tonumber(str)
if num then
return num
else
return def
end
end
--- Converts a string to a number or throws an error with a specified message if conversion fails.
-- @param str (string) The string to convert to a number.
-- @param errorMsg (string) [optional] The error message to throw if conversion fails. Defaults to "NaN".
-- @return (number) The numeric value represented by the input string.
-- @throws Error with specified error message if conversion fails.
function p.toNumberOrError(str, errorMsg)
local num = tonumber(str)
local msg = errorMsg
if msg == nil then
msg = "NaN"
end
if num then
return num
else
error(msg)
end
end
-- Returns the probability of getting at least one drop, provided
-- the drop rate and the current amount of drops.
function p.getDropProbability(dropRate, totalDrops)
dropRate = p.clamp(dropRate, 0, 1)
return 1 - (1- dropRate) ^ 500
end
-- Returns the amount of drops required for a certain drop probability.
function p.getDropsForProbability(dropRate, probability)
dropRate = p.clamp(dropRate, 0, 1)
probability = p.clamp(probability, 0, 1)
return math.log(1 - probability) / math.log(1 - dropRate)
end
return p