置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Module:Math

贴贴♀百科,万娘皆可贴的百科全书!转载请标注来源页面的网页链接,并声明引自贴贴百科。内容不可商用。
跳到导航 跳到搜索
Template-info.svg 模块文档  [创建] [刷新]
  1. --[[
  2. 引自维基百科(enwp:Module:Math,oldid=613638641)
  3. This module provides a number of basic mathematical operations.
  4. ]]
  5. local yesno, getArgs -- lazily initialized
  6. local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.
  7. local wrap = {} -- Holds wrapper functions that process arguments from #invoke. These act as intemediary between functions meant for #invoke and functions meant for Lua.
  8. --[[
  9. Helper functions used to avoid redundant code.
  10. ]]
  11. local function err(msg)
  12. -- Generates wikitext error messages.
  13. return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg)
  14. end
  15. local function unpackNumberArgs(args)
  16. -- Returns an unpacked list of arguments specified with numerical keys.
  17. local ret = {}
  18. for k, v in pairs(args) do
  19. if type(k) == 'number' then
  20. table.insert(ret, v)
  21. end
  22. end
  23. return unpack(ret)
  24. end
  25. local function makeArgArray(...)
  26. -- Makes an array of arguments from a list of arguments that might include nils.
  27. local args = {...} -- Table of arguments. It might contain nils or non-number values, so we can't use ipairs.
  28. local nums = {} -- Stores the numbers of valid numerical arguments.
  29. local ret = {}
  30. for k, v in pairs(args) do
  31. v = p._cleanNumber(v)
  32. if v then
  33. nums[#nums + 1] = k
  34. args[k] = v
  35. end
  36. end
  37. table.sort(nums)
  38. for i, num in ipairs(nums) do
  39. ret[#ret + 1] = args[num]
  40. end
  41. return ret
  42. end
  43. local function applyFuncToArgs(func, ...)
  44. -- Use a function on all supplied arguments, and return the result. The function must accept two numbers as parameters,
  45. -- and must return a number as an output. This number is then supplied as input to the next function call.
  46. local vals = makeArgArray(...)
  47. local count = #vals -- The number of valid arguments
  48. if count == 0 then return
  49. -- Exit if we have no valid args, otherwise removing the first arg would cause an error.
  50. nil, 0
  51. end
  52. local ret = table.remove(vals, 1)
  53. for _, val in ipairs(vals) do
  54. ret = func(ret, val)
  55. end
  56. return ret, count
  57. end
  58. --[[
  59. random
  60. Generate a random number
  61. Usage:
  62. {{#invoke: Math | random }}
  63. {{#invoke: Math | random | maximum value }}
  64. {{#invoke: Math | random | minimum value | maximum value }}
  65. ]]
  66. function wrap.random(args)
  67. local first = p._cleanNumber(args[1])
  68. local second = p._cleanNumber(args[2])
  69. return p._random(first, second)
  70. end
  71. function p._random(first, second)
  72. math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000))
  73. -- math.random will throw an error if given an explicit nil parameter, so we need to use if statements to check the params.
  74. if first and second then
  75. if first <= second then -- math.random doesn't allow the first number to be greater than the second.
  76. return math.random(first, second)
  77. end
  78. elseif first then
  79. return math.random(first)
  80. else
  81. return math.random()
  82. end
  83. end
  84. --[[
  85. order
  86. Determine order of magnitude of a number
  87. Usage:
  88. {{#invoke: Math | order | value }}
  89. ]]
  90. function wrap.order(args)
  91. local input_string = (args[1] or args.x or '0');
  92. local input_number = p._cleanNumber(input_string);
  93. if input_number == nil then
  94. return err('order of magnitude input appears non-numeric')
  95. else
  96. return p._order(input_number)
  97. end
  98. end
  99. function p._order(x)
  100. if x == 0 then return 0 end
  101. return math.floor(math.log10(math.abs(x)))
  102. end
  103. --[[
  104. precision
  105. Detemines the precision of a number using the string representation
  106. Usage:
  107. {{ #invoke: Math | precision | value }}
  108. ]]
  109. function wrap.precision(args)
  110. local input_string = (args[1] or args.x or '0');
  111. local trap_fraction = args.check_fraction;
  112. local input_number;
  113. if not yesno then
  114. yesno = require('Module:Yesno')
  115. end
  116. if yesno(trap_fraction, true) then -- Returns true for all input except nil, false, "no", "n", "0" and a few others. See [[Module:Yesno]].
  117. local pos = string.find(input_string, '/', 1, true);
  118. if pos ~= nil then
  119. if string.find(input_string, '/', pos + 1, true) == nil then
  120. local denominator = string.sub(input_string, pos+1, -1);
  121. local denom_value = tonumber(denominator);
  122. if denom_value ~= nil then
  123. return math.log10(denom_value);
  124. end
  125. end
  126. end
  127. end
  128. input_number, input_string = p._cleanNumber(input_string);
  129. if input_string == nil then
  130. return err('precision input appears non-numeric')
  131. else
  132. return p._precision(input_string)
  133. end
  134. end
  135. function p._precision(x)
  136. if type(x) == 'number' then
  137. x = tostring(x)
  138. end
  139. x = string.upper(x)
  140. local decimal = x:find('%.')
  141. local exponent_pos = x:find('E')
  142. local result = 0;
  143. if exponent_pos ~= nil then
  144. local exponent = string.sub(x, exponent_pos + 1)
  145. x = string.sub(x, 1, exponent_pos - 1)
  146. result = result - tonumber(exponent)
  147. end
  148. if decimal ~= nil then
  149. result = result + string.len(x) - decimal
  150. return result
  151. end
  152. local pos = string.len(x);
  153. while x:byte(pos) == string.byte('0') do
  154. pos = pos - 1
  155. result = result - 1
  156. if pos <= 0 then
  157. return 0
  158. end
  159. end
  160. return result
  161. end
  162. --[[
  163. max
  164. Finds the maximum argument
  165. Usage:
  166. {{#invoke:Math| max | value1 | value2 | ... }}
  167. Note, any values that do not evaluate to numbers are ignored.
  168. ]]
  169. function wrap.max(args)
  170. return p._max(unpackNumberArgs(args))
  171. end
  172. function p._max(...)
  173. local function maxOfTwo(a, b)
  174. if a > b then
  175. return a
  176. else
  177. return b
  178. end
  179. end
  180. local max_value = applyFuncToArgs(maxOfTwo, ...)
  181. if max_value then
  182. return max_value
  183. end
  184. end
  185. --[[
  186. min
  187. Finds the minimum argument
  188. Usage:
  189. {{#invoke:Math| min | value1 | value2 | ... }}
  190. OR
  191. {{#invoke:Math| min }}
  192. When used with no arguments, it takes its input from the parent
  193. frame. Note, any values that do not evaluate to numbers are ignored.
  194. ]]
  195. function wrap.min(args)
  196. return p._min(unpackNumberArgs(args))
  197. end
  198. function p._min(...)
  199. local function minOfTwo(a, b)
  200. if a < b then
  201. return a
  202. else
  203. return b
  204. end
  205. end
  206. local min_value = applyFuncToArgs(minOfTwo, ...)
  207. if min_value then
  208. return min_value
  209. end
  210. end
  211. --[[
  212. average
  213. Finds the average
  214. Usage:
  215. {{#invoke:Math| average | value1 | value2 | ... }}
  216. OR
  217. {{#invoke:Math| average }}
  218. Note, any values that do not evaluate to numbers are ignored.
  219. ]]
  220. function wrap.average(args)
  221. return p._average(unpackNumberArgs(args))
  222. end
  223. function p._average(...)
  224. local function getSum(a, b)
  225. return a + b
  226. end
  227. local sum, count = applyFuncToArgs(getSum, ...)
  228. if not sum then
  229. return 0
  230. else
  231. return sum / count
  232. end
  233. end
  234. --[[
  235. round
  236. Rounds a number to specified precision
  237. Usage:
  238. {{#invoke:Math | round | value | precision }}
  239. --]]
  240. function wrap.round(args)
  241. local value = p._cleanNumber(args[1] or args.value or 0)
  242. local precision = p._cleanNumber(args[2] or args.precision or 0)
  243. if value == nil or precision == nil then
  244. return err('round input appears non-numeric')
  245. else
  246. return p._round(value, precision)
  247. end
  248. end
  249. function p._round(value, precision)
  250. local rescale = math.pow(10, precision or 0);
  251. return math.floor(value * rescale + 0.5) / rescale;
  252. end
  253. --[[
  254. mod
  255. Implements the modulo operator
  256. Usage:
  257. {{#invoke:Math | mod | x | y }}
  258. --]]
  259. function wrap.mod(args)
  260. local x = p._cleanNumber(args[1])
  261. local y = p._cleanNumber(args[2])
  262. if not x then
  263. return err('first argument to mod appears non-numeric')
  264. elseif not y then
  265. return err('second argument to mod appears non-numeric')
  266. else
  267. return p._mod(x, y)
  268. end
  269. end
  270. function p._mod(x, y)
  271. local ret = x % y
  272. if not (0 <= ret and ret < y) then
  273. ret = 0
  274. end
  275. return ret
  276. end
  277. --[[
  278. gcd
  279. Calculates the greatest common divisor of multiple numbers
  280. Usage:
  281. {{#invoke:Math | gcd | value 1 | value 2 | value 3 | ... }}
  282. --]]
  283. function wrap.gcd(args)
  284. return p._gcd(unpackNumberArgs(args))
  285. end
  286. function p._gcd(...)
  287. local function findGcd(a, b)
  288. local r = b
  289. local oldr = a
  290. while r ~= 0 do
  291. local quotient = math.floor(oldr / r)
  292. oldr, r = r, oldr - quotient * r
  293. end
  294. if oldr < 0 then
  295. oldr = oldr * -1
  296. end
  297. return oldr
  298. end
  299. local result, count = applyFuncToArgs(findGcd, ...)
  300. return result
  301. end
  302. --[[
  303. precision_format
  304. Rounds a number to the specified precision and formats according to rules
  305. originally used for {{template:Rnd}}. Output is a string.
  306. Usage:
  307. {{#invoke: Math | precision_format | number | precision }}
  308. ]]
  309. function wrap.precision_format(args)
  310. local value_string = args[1] or 0
  311. local precision = args[2] or 0
  312. return p._precision_format(value_string, precision)
  313. end
  314. function p._precision_format(value_string, precision)
  315. -- For access to Mediawiki built-in formatter.
  316. local lang = mw.getContentLanguage();
  317. local value
  318. value, value_string = p._cleanNumber(value_string)
  319. precision = p._cleanNumber(precision)
  320. -- Check for non-numeric input
  321. if value == nil or precision == nil then
  322. return err('invalid input when rounding')
  323. end
  324. local current_precision = p._precision(value)
  325. local order = p._order(value)
  326. -- Due to round-off effects it is neccesary to limit the returned precision under
  327. -- some circumstances because the terminal digits will be inaccurately reported.
  328. if order + precision >= 14 then
  329. orig_precision = p._precision(value_string)
  330. if order + orig_precision >= 14 then
  331. precision = 13 - order;
  332. end
  333. end
  334. -- If rounding off, truncate extra digits
  335. if precision < current_precision then
  336. value = p._round(value, precision)
  337. current_precision = p._precision(value)
  338. end
  339. local formatted_num = lang:formatNum(math.abs(value))
  340. local sign
  341. -- Use proper unary minus sign rather than ASCII default
  342. if value < 0 then
  343. sign = '−'
  344. else
  345. sign = ''
  346. end
  347. -- Handle cases requiring scientific notation
  348. if string.find(formatted_num, 'E', 1, true) ~= nil or math.abs(order) >= 9 then
  349. value = value * math.pow(10, -order)
  350. current_precision = current_precision + order
  351. precision = precision + order
  352. formatted_num = lang:formatNum(math.abs(value))
  353. else
  354. order = 0;
  355. end
  356. formatted_num = sign .. formatted_num
  357. -- Pad with zeros, if needed
  358. if current_precision < precision then
  359. local padding
  360. if current_precision <= 0 then
  361. if precision > 0 then
  362. local zero_sep = lang:formatNum(1.1)
  363. formatted_num = formatted_num .. zero_sep:sub(2,2)
  364. padding = precision
  365. if padding > 20 then
  366. padding = 20
  367. end
  368. formatted_num = formatted_num .. string.rep('0', padding)
  369. end
  370. else
  371. padding = precision - current_precision
  372. if padding > 20 then
  373. padding = 20
  374. end
  375. formatted_num = formatted_num .. string.rep('0', padding)
  376. end
  377. end
  378. -- Add exponential notation, if necessary.
  379. if order ~= 0 then
  380. -- Use proper unary minus sign rather than ASCII default
  381. if order < 0 then
  382. order = '−' .. lang:formatNum(math.abs(order))
  383. else
  384. order = lang:formatNum(order)
  385. end
  386. formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>'
  387. end
  388. return formatted_num
  389. end
  390. --[[
  391. Helper function that interprets the input numerically. If the
  392. input does not appear to be a number, attempts evaluating it as
  393. a parser functions expression.
  394. ]]
  395. function p._cleanNumber(number_string)
  396. if type(number_string) == 'number' then
  397. -- We were passed a number, so we don't need to do any processing.
  398. return number_string, tostring(number_string)
  399. elseif type(number_string) ~= 'string' or not number_string:find('%S') then
  400. -- We were passed a non-string or a blank string, so exit.
  401. return nil, nil;
  402. end
  403. -- Attempt basic conversion
  404. local number = tonumber(number_string)
  405. -- If failed, attempt to evaluate input as an expression
  406. if number == nil then
  407. local success, result = pcall(mw.ext.ParserFunctions.expr, number_string)
  408. if success then
  409. number = tonumber(result)
  410. number_string = tostring(number)
  411. else
  412. number = nil
  413. number_string = nil
  414. end
  415. else
  416. number_string = number_string:match("^%s*(.-)%s*$") -- String is valid but may contain padding, clean it.
  417. number_string = number_string:match("^%+(.*)$") or number_string -- Trim any leading + signs.
  418. if number_string:find('^%-?0[xX]') then
  419. -- Number is using 0xnnn notation to indicate base 16; use the number that Lua detected instead.
  420. number_string = tostring(number)
  421. end
  422. end
  423. return number, number_string
  424. end
  425. --[[
  426. Wrapper function that does basic argument processing. This ensures that all functions from #invoke can use either the current
  427. frame or the parent frame, and it also trims whitespace for all arguments and removes blank arguments.
  428. ]]
  429. local mt = { __index = function(t, k)
  430. return function(frame)
  431. if not getArgs then
  432. getArgs = require('Module:Arguments').getArgs
  433. end
  434. return wrap[k](getArgs(frame)) -- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed.
  435. end
  436. end }
  437. return setmetatable(p, mt)