Files
rappaurio-sae501_502/app/node_modules/cejs/data/math.js
2023-09-25 13:27:24 +02:00

4315 lines
120 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @name CeL function for mathematics.
* @fileoverview 本檔案包含了數學演算相關的 functions。
*
* TODO: 方程式圖形顯示 by SVG
*
* @see http://www.wolframalpha.com/<br />
* http://www.numberempire.com/
*/
// More examples: see /_test suite/test.js
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// module name
name : 'data.math',
require : 'data.code.compatibility.|data.native.set_bind',
// 設定不匯出的子函式。
// no_extend : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var set_bind = this.r('set_bind');
var has_bigint = library_namespace.env.has_bigint;
/**
* null module constructor
*
* @class 數學相關的 functions
*/
var _// JSDT:_module_
= function() {
// null module constructor
};
/**
* for JSDT: 有 prototype 才會將之當作 Class
*/
_// JSDT:_module_
.prototype = {};
/**
* <code>
數位
十分位 tenths digit
整數 whole number
</code>
*/
// ---------------------------------------------------------------------//
// basic constants. 定義基本常數。
var
/**
* empty product, or nullary product, 乘法單位元素.<br />
* number * MULTIPLICATIVE_IDENTITY === number.<br />
* 2/2, 3/3, ..
*
* MULTIPLICATIVE_IDENTITY = 1
*
* @type {Number}
* @constant
*
* @see https://en.wikipedia.org/wiki/Identity_element
* https://en.wikipedia.org/wiki/Empty_product
*/
MULTIPLICATIVE_IDENTITY = 1 / 1,
/**
* Any nonzero number raised by the exponent 0 is 1.<br />
* (any number) ^ 0 === Math.pow(number, 0) === ZERO_EXPONENT<br />
* Math.pow(2, 0), Math.pow(3, 0), ..
*
* ZERO_EXPONENT = 1
*
* @type {Number}
* @constant
*
* @see https://en.wikipedia.org/wiki/Exponentiation
*/
ZERO_EXPONENT = Math.pow(1, 0),
/**
* absorbing element, zero element.<br />
* number * ABSORBING_ELEMENT === ABSORBING_ELEMENT<br />
* Math.pow(2, 0), Math.pow(3, 0), ..
*
* @type {Number}
* @constant
*
* @see https://en.wikipedia.org/wiki/Absorbing_element
*/
ABSORBING_ELEMENT = 0,
/**
* multiplication sign. e.g., '⋅', '*', '×'.
*
* @type {String}
* @constant
*
* @see https://en.wikipedia.org/wiki/Multiplication_sign
* https://en.wikipedia.org/wiki/Interpunct
*/
MULTIPLICATION_SIGN = '⋅',
/**
* default base = 10.<br />
* 內定10位數。應與 parseInt() 一致。
*
* @type {Natural}
* @constant
*/
DEFAULT_BASE = parseInt('10'),
/**
* The biggest integer we can square. 超過此數則無法安全操作平方。
*
* @type {Natural}
* @constant
*/
sqrt_max_integer = Math.sqrt(Number.MAX_SAFE_INTEGER) | 0;
// ---------------------------------------------------------------------//
/**
* use Horner's method to calculate the value of polynomial.
*
* @param {Array}coefficients
* coefficients of polynomial.<br />
* coefficients: [ degree 0, degree 1, degree 2, ... ]
* @param {Number}variable
* value of (x)
*
* @returns {Number} the value of polynomial
*
* @see https://en.wikipedia.org/wiki/Horner%27s_method
*/
function polynomial_value(coefficients, variable) {
return coefficients.reduceRight(function(value, coefficient) {
return value * variable + coefficient;
});
}
_.polynomial_value = polynomial_value;
_// JSDT:_module_
.
/**
* 輾轉相除 n1/n2 或 小數 n1/1 轉成 整數/整數。
*
* @param {Natural}n1
* number 1
* @param {Natural}[n2]
* number 2
* @param {Natural}times
* maximum times 次數, 1,2,..
*
* @return {Array} 連分數序列 (continued fraction) ** 負數視 _.mutual_division.done
* 而定!
*/
mutual_division = function mutual_division(n1, n2, times) {
var q = [], c;
if (isNaN(times) || times <= 0)
times = 80;
if (!n2 || isNaN(n2))
n2 = 1;
if (!Number.isInteger(n1)) {
c = n1;
var i = 9, f = n2;
while (i--) {
// 以整數運算比較快這樣會造成整數多4%浮點數多1/3倍的時間但仍值得。
f *= DEFAULT_BASE;
c *= DEFAULT_BASE;
if (Number.isInteger(c)) {
n1 = c;
n2 = f;
break;
}
}
}
// 連分數負數之處理。更沒問題的: (n1 < 0?1:0) ^ (n2 < 0?1:0)
if (_.mutual_division.mode && ((n1 < 0) ^ (n2 < 0))) {
// 使兩數皆為正
if (n2 < 0)
n2 = -n2;
else
n1 = -n1;
q.push(-(1 + (n1 - (c = n1 % n2)) / n2));
n1 = n2;
n2 -= c;
}
// old:
if (false) {
while (b && n--) {
// 2.08s@10000
// 可能因為少設定(=一次c所以較快。但若輸入不為整數不確保d為整數用Math.floor((a-(c=a%b))/b)可確保,速度與下式一樣快。
c = a % b;
d.push((a - c) / b);
a = b;
b = c;
// 2.14s@10000:mutual_division(.142857)
// d.push(c=Math.floor(a/b)),c=a-b*c,a=b,b=c;
// 2.2s@10000
// d.push(Math.floor(a/b)),b=a%(c=b),a=c;
}
if (n)
d.push(0);
}
// 2.4s@10000
// 可能因為少設定(=一次c所以較快。但若輸入不為整數不確保d為整數用Math.floor((a-(c=a%b))/b)可確保,速度與下式一樣快。
while (times--)
if (n2) {
c = n1 % n2;
q.push((n1 - c) / n2);
n1 = n2;
n2 = c;
} else {
// [ ... , done mark, (最後非零的餘數。若原 n1, n2 皆為整數,則此值為
// GCD。但請注意:這邊是已經經過前面為了以整數運算,增加倍率過的數值!!) ]
q.push(_.mutual_division.done, n1);
// library_namespace.debug('done: ' + q);
break;
}
/**
* <code>
// 2.26s@10000
while(b&&n--)if(d.push((a-(c=a%b))/b),a=b,!(b=c)){d.push(0);break;}
var m=1;c=1;while(m&&n--)d.push(m=++c%2?b?(a-(a%=b))/b:0:a?(b-(b%=a))/a:0);//buggy
</code>
*/
return q;
};
_// JSDT:_module_
.mutual_division.done = -7;// ''
_// JSDT:_module_
.
/**
* !!mode:連分數處理,對負數僅有最初一數為負。
*/
mutual_division.mode = 0;
_// JSDT:_module_
.
/**
* 取得連分數序列的數值。
*
* @param {Array}sequence
* 序列
* @param {Natural}[max_no]
* maximum no. 取至第 max_no 個
*
* @return {Array}連分數序列的數值
*
* @requires mutual_division.done
*/
continued_fraction = function(sequence, max_no) {
if (!Array.isArray(sequence) || !sequence.length)
return sequence;
if (sequence.at(-2) === _.mutual_division.done)
sequence.length -= 2;
if (sequence.length < 1)
return sequence;
if (!max_no/* || max_no < 2 */|| max_no > sequence.length)
max_no = sequence.length;
var a, b;
if (max_no % 2) {
b = 1;
a = 0;
} else {
a = 1;
b = 0;
}
if (false) {
sequence[max_no++] = 1;
if (--max_no % 2) {
b = sequence[max_no];
a = s[--max_no];
} else {
a = sequence[max_no];
b = sequence[--max_no];
}
}
if (false)
library_namespace.debug('a=' + a + ', b=' + b + ', max_no='
+ max_no);
while (max_no--)
if (max_no % 2)
b += a * sequence[max_no];
else
a += b * sequence[max_no];
if (false)
library_namespace.debug('a=' + a + ', b=' + b);
return [ a, b ];
};
// quadratic (m√r + i) / D → continued fraction [... , [period ...]]
// Rosen, Kenneth H. (2011). Elementary Number Theory and its Applications
// (6th edition). Boston: Pearson Addison-Wesley. pp. 508511.
// https://en.wikipedia.org/wiki/Periodic_continued_fraction
// https://en.wikipedia.org/wiki/Square_root_of_2
// https://en.wikipedia.org/wiki/Square_root#As_periodic_continued_fractions
// https://en.wikipedia.org/wiki/Generalized_continued_fraction#Roots_of_positive_numbers
function quadratic_to_continued_fraction(r, m, i, D) {
if (r < 0) {
throw 'The root is negative!';
}
if (!i)
i = 0;
if (!D)
D = 1;
if (!m)
m = 1;
else if (m < 0) {
m = -m;
i = -i;
D = -D;
}
// (m√r + i) / D
// = (√(r m^2) + i) / D
// = (√(d in book) + P0) / Q0
var d = m * m * r,
//
P = i, Q = D,
// A: α in book.
A, a, sequence = [], ptr = sequence, start_PQ;
// Be sure Q0 | (d - P0^2)
if ((d - P * P) % Q !== 0)
P *= Q, d *= Q * Q, Q *= Q;
// assert: now: Q0 | (d - P0^2)
for (var sqrt = Math.sqrt(d), t;;) {
if (start_PQ) {
if (P === start_PQ[0] && Q === start_PQ[1])
return sequence;
} else if (0 < (t = sqrt - P) && t < Q) {
// test if α is purely periodic.
start_PQ = [ P, Q ];
sequence.push(ptr = []);
}
ptr.push(a = Math.floor(A = (sqrt + P) / Q));
library_namespace.debug(((sequence === ptr ? 0
: sequence.length - 1)
+ ptr.length - 1)
+ ': P='
+ P
+ ', Q='
+ Q
+ ', α≈'
+ (DEFAULT_BASE * A | 0)
/ DEFAULT_BASE + ', a=' + a, 3);
// set next Pn = a(n-1)Q(n-1) - P(n-1), Qn = (d - Pn^2) / Q(n-1).
P = a * Q - P;
Q = (d - P * P) / Q;
if (Q === 0)
// is not a quadratic irrationality?
return sequence;
// assert: Pn, Qn are both integers.
}
}
_.quadratic_to_continued_fraction = quadratic_to_continued_fraction;
// get the first solution of Pell's equation: x^2 - d y^2 = 1 or -1.
// https://en.wikipedia.org/wiki/Pell%27s_equation
// Rosen, Kenneth H. (2005). Elementary Number Theory and its Applications
// (5th edition). Boston: Pearson Addison-Wesley. pp. 542-545.
function solve_Pell(d, n, NO) {
// TODO
// use CeL.data.math.quadratic.solve_Pell instead
;
}
// _.solve_Pell = solve_Pell;
_// JSDT:_module_
.
/**
* The best rational approximation. 取得值最接近之有理數 (use 連分數 continued fraction),
* 取近似值. c.f., 調日法 在分子或分母小於下一個漸進分數的分數中,其值是最接近精準值的近似值。
*
* @param {Number}number
* number
* @param {Number}[rate]
* 比例在 rate 以上
* @param {Natural}[max_no]
* maximum no. 最多取至序列第 max_no 個 TODO : 並小於 l: limit
*
* @return {Array}[分子, 分母, 誤差]
*
* @requires mutual_division,continued_fraction
* @see https://en.wikipedia.org/wiki/Continued_fraction#Best_to_rational_numbers
*/
to_rational_number = function(number, rate, max_no) {
if (!rate)
// This is a magic number: 我們無法準確得知其界限為何。
rate = 65536;
var d = _
.mutual_division(number, 1, max_no && max_no > 0 ? max_no : 20), i = 0, a, b = d[0], done = _.mutual_division.done;
if (!b)
b = d[++i];
while (++i < d.length && (a = d[i]) !== done)
if (a / b < rate)
b = a;
else
break;
if (false)
library_namespace.debug(number
+ ' '
+
// 連分數表示 (continued fraction)
(d.length > 1 && d.at(-2) === _.mutual_division.done ? '='
+ ' [<em>'
+ d[0]
+ ';'
+ d.slice(1, i).join(', ')
+ '</em>'
+ (i < d.length - 2 ? ', '
+ d.slice(i, -2).join(', ') : '')
+ '] ... ' + d.slice(-1)
:
// 約等於的符號是≈或≒,不等於的符號是≠。
// https://zh.wikipedia.org/wiki/%E7%AD%89%E4%BA%8E
'≈'
+ ' [<em>'
+ d[0]
+ ';'
+ d.slice(1, i).join(', ')
+ '</em>'
+ (i < d.length ? ', '
+ d.slice(i).join(', ') : '')
+ ']: ' + d.length + ',' + i + ',' + d[i]));
d = _.continued_fraction(d, i);
if (d[1] < 0) {
d[0] = -d[0];
d[1] = -d[1];
}
if (false)
library_namespace.debug('→ ' + d[0] + '/' + d[1]);
// [ {Integer}±numerator, {Natural}denominator ]
return [ d[0], d[1], d[0] / d[1] - number ];
};
// 正規化帶分數。 to_mixed_fraction
// 2019/7/10 14:22:6
// mixed_fraction =
// [ {Integer}±whole, {Integer}±numerator, {Natural|Undefined}denominator ]
function normalize_mixed_fraction(mixed_fraction) {
if (typeof mixed_fraction === 'number') {
// treat as float
mixed_fraction = [ mixed_fraction ];
}
var whole = mixed_fraction[0] || ABSORBING_ELEMENT;
var numerator = mixed_fraction[1] || ABSORBING_ELEMENT;
var denominator = mixed_fraction[2] || MULTIPLICATIVE_IDENTITY;
// {Natural}denominator >= 1
if (denominator < 0) {
denominator = -denominator;
numerator = -numerator;
}
// {Natural}denominator ∈
if (!Number.isInteger(denominator)) {
// assert: is float
denominator = _.to_rational_number(denominator);
numerator *= typeof numerator === 'bigint' ? BigInt(denominator[1])
: denominator[1];
denominator = denominator[0];
}
// {Integer}±numerator ∈
if (!Number.isInteger(numerator)) {
// assert: is float
numerator = _.to_rational_number(numerator);
denominator *= typeof denominator === 'bigint' ? BigInt(numerator[1])
: numerator[1];
numerator = numerator[0];
}
// assert: {Natural}denominator, {Integer}±numerator
// TODO: 約分here。
var using_bigint;
// {Integer}±whole ∈
if (typeof whole === 'number' && !Number.isInteger(whole)) {
// assert: whole is float
whole = _.to_rational_number(whole);
var LCM = _.LCM(denominator, whole[1]);
if (typeof LCM === 'bigint') {
using_bigint = true;
// convert all numbers to the same type.
numerator = BigInt(numerator);
denominator = BigInt(denominator);
whole = whole.map(BigInt);
}
numerator = numerator
// (LCM / denominator) === GCD * whole[1]
* (LCM / denominator)
+ (whole[0] < 0 ? -(-whole[0] % whole[1]) : whole[0]
% whole[1]) * (LCM / whole[1]);
denominator = LCM;
if (using_bigint) {
whole = whole[0] / whole[1];
} else {
whole = whole[0] < 0 ? -Math.floor(-whole[0] / whole[1]) : Math
.floor(whole[0] / whole[1]);
}
}
// TODO: convert all numbers to the same type.
// whole, numerator 必須同符號。
if (whole * numerator < 0) {
if (whole < 0) {
whole++;
// numerator > 0
numerator -= denominator;
} else {
whole--;
// numerator < 0
numerator += denominator;
}
}
// 處理假分數。同時會處理絕對值為整數之問題。
if (Math.absolute(numerator) >= denominator) {
whole += using_bigint ? numerator / denominator : Math
.floor(numerator / denominator);
numerator %= denominator;
}
// 約分。
if (numerator == ABSORBING_ELEMENT) {
// normalize
denominator = MULTIPLICATIVE_IDENTITY;
} else {
var GCD = _.GCD(numerator, denominator);
if (GCD >= 2) {
if (using_bigint)
GCD = BigInt(GCD);
numerator /= GCD;
denominator /= GCD;
}
}
// export
mixed_fraction = Object.assign([ whole, numerator, denominator ], {
valueOf : mixed_fraction_valueOf,
toString : mixed_fraction_toString
});
return mixed_fraction;
}
function mixed_fraction_valueOf() {
if (!this[1])
return this[0];
return this[0] + this[1] / this[2];
}
function mixed_fraction_toString() {
if (!this[1])
return String(this[0]);
if (!this[0])
return this[1] + '/' + this[2];
return this[0] + (this[1] < 0 ? '' : '+') + this[1] + '/' + this[2];
}
_.normalize_mixed_fraction = normalize_mixed_fraction;
// ------------------------------------------------------------------------
// 正規化數字成 integer 或 bigint
// 在大量計算前,盡可能先轉換成普通 {Number} 以加快速度。
// cohandler(may convert to number)
function to_int_or_bigint(value, cohandler) {
var number;
if (typeof value === 'bigint') {
number = Number(value);
if (Number.isSafeInteger(number)) {
cohandler && cohandler(true);
return number;
} else {
cohandler && cohandler(false);
return value;
}
}
// 這方法無法準確處理像 `1e38/7`, `10/7` 這樣的情況。
if (typeof value === 'number') {
number = Math.round(value);
if (!Number.isSafeInteger(number)) {
throw new RangeError('Cannot convert number ' + value
+ ' to safe integer!');
}
cohandler && cohandler(true);
return Math.round(number);
}
number = parseInt(value);
if (Number.isSafeInteger(number)) {
cohandler && cohandler(true);
return number;
}
if (!has_bigint)
throw new RangeError('Cannot convert ' + number
+ ' to safe integer!');
cohandler && cohandler(false);
return BigInt(value);
}
// Let all elements of {Array}this the same type: int, else bigint.
// 可能的話應該將絕對值最大的數字放在前面,早點判別出是否需要用 {BigInt}。
function array_to_int_or_bigint() {
// assert: {Array}this
// cache int values
if (this.some(function(value, index) {
value = to_int_or_bigint(value);
this[index] = value;
return typeof value === 'bigint';
}, this)) {
// must using bigint
this.forEach(function(value, index) {
this[index] = BigInt(value);
}, this);
}
// assert: all elements of `this` is in the same type.
// typeof this[0] === typeof this[1]
return this;
}
// 可用於 {BigInt} 之 Math.abs
// https://en.wikipedia.org/wiki/Absolute_value
function absolute(value) {
return value < 0 ? -value : value;
}
Math.absolute = absolute;
/**
* 求多個數之 GCD(Greatest Common Divisor, 最大公因數/公約數).<br />
* Using Euclidean algorithm(輾轉相除法).<br />
*
* TODO: 判斷互質.
*
* @param {Integers}number_array
* number array
*
* @returns {Natural} GCD of the numbers specified
*/
function GCD(number_array) {
if (arguments.length > 1) {
// Array.from()
number_array = Array.prototype.slice.call(arguments);
}
// 正規化數字。
number_array = number_array.map(function(value) {
return Math.absolute(to_int_or_bigint(value));
})
// 由小至大排序可以減少計算次數?? 最起碼能夠延後使用 {BigInt} 的時機。
.sort(library_namespace.general_ascending)
// .unique_sorted()
;
// console.log(number_array);
// 不在此先設定 gcd = number_array[0],是為了讓每個數字通過資格檢驗。
var index = 0, length = number_array.length, gcd = 0, remainder, number;
// assert: 所有數字皆已先轉換成數字,並已轉為絕對值。
while (index < length) {
number = number_array[index++];
if (number >= 1) {
gcd = number;
break;
}
}
// console.log(gcd);
while (index < length && 2 <= gcd) {
number = number_array[index++];
if (!(number >= 1))
continue;
if (typeof number === 'bigint') {
number %= BigInt(gcd);
// [ gcd, number ] = [ gcd, number ].to_int_or_bigint();
remainder = [ gcd, number ].to_int_or_bigint();
gcd = remainder[0];
number = remainder[1];
}
// assert: typeof gcd === typeof number
// console.log([ gcd, number ]);
// Euclidean algorithm 輾轉相除法。
while ((remainder = number % gcd) >= 1) {
number = gcd;
// 使用絕對值最小的餘數。為了要處理 {BigInt},因此不採用 Math.min()。
// gcd = Math.min(remainder, gcd - remainder);
gcd = gcd - remainder < remainder ? gcd - remainder : remainder;
}
}
if (typeof gcd === 'bigint'
&& Number.isSafeInteger(number = Number(gcd))) {
gcd = number;
}
return gcd;
}
_// JSDT:_module_
.GCD = GCD;
_// JSDT:_module_
.
/**
* 求多個數之 LCM(Least Common Multiple, 最小公倍數): method 1.<br />
* Using 類輾轉相除法.<br />
*
* @param {Integers}number_array
* number array
*
* @returns {Natural} LCM of the numbers specified
*/
LCM = function LCM(number_array) {
if (arguments.length > 1) {
// Array.from()
number_array = Array.prototype.slice.call(arguments);
}
// 正規化數字。
number_array = number_array.map(function(value) {
return Math.absolute(to_int_or_bigint(value));
})
// .sort().reverse()
;
if (number_array.some(function(number) {
return number == 0;
})) {
// 允許 0:
return 0;
}
var lcm = number_array[0];
for (var index = 1, length = number_array.length; index < length; index++) {
var number = number_array[index];
// assert: {Integer}number
var gcd = _.GCD(number, lcm);
if (typeof number === typeof gcd) {
number /= gcd;
if (typeof number === typeof lcm) {
gcd = lcm * number;
if (Number.isSafeInteger(gcd)) {
lcm = gcd;
} else if (has_bigint) {
lcm = BigInt(lcm) * BigInt(number);
} else {
throw new RangeError('LCM is not safe integer!');
}
} else {
// assert: {BigInt}number or {BigInt}lcm
lcm = BigInt(lcm) * BigInt(number);
}
} else {
// assert: {BigInt}number, {Number}gcd
lcm = BigInt(lcm) * (number / BigInt(gcd));
}
}
return lcm;
};
_// JSDT:_module_
.
/**
* 求多個數之 LCM(Least Common Multiple, 最小公倍數): method 1.<br />
* Using 類輾轉相除法.<br />
*
* TODO: 更快的方法: 短除法? 一次算出 GCD, LCM?
*
* @param {Integers}number_array
* number array
*
* @returns {Natural} LCM of the numbers specified
*/
LCM3 = function LCM3(number_array) {
if (arguments.length > 1) {
// Array.from()
number_array = Array.prototype.slice.call(arguments);
}
// 正規化數字。
number_array = number_array.map(function(value) {
return Math.absolute(to_int_or_bigint(value));
})
// .sort().reverse()
;
if (number_array.some(function(number) {
return number == 0;
})) {
// 允許 0:
return 0;
}
var lcm = number_array[0], using_bigint;
for (var index = 1, length = number_array.length; index < length; index++) {
var number = number_array[index];
// assert: {Integer}number
if (typeof number !== typeof lcm) {
// assert: number, lcm 有一個是 bigint。
using_bigint = true;
lcm = BigInt(lcm);
number = BigInt(number);
}
// console.log([ lcm, number ]);
var number0 = number;
var lcm0 = lcm;
// 倒反版的 Euclidean algorithm 輾轉相除法.
// 反覆讓兩方各自加到比對方大的倍數,當兩者相同時,即為 lcm。
while (lcm !== number) {
// console.log([ lcm0, number0, lcm, number ]);
if (lcm > number) {
var remainder = -lcm % number0;
if (remainder) {
number = lcm + remainder + number0;
if (!using_bigint && has_bigint
&& !Number.isSafeInteger(number)) {
using_bigint = true;
number0 = BigInt(number0);
lcm0 = BigInt(lcm0);
number = BigInt(lcm + remainder) + number0;
lcm = BigInt(lcm);
}
} else {
// number0 整除 lcm: 取 lcm 即可.
break;
}
} else {
var remainder = -number % lcm0;
if (remainder) {
lcm = number + remainder + lcm0;
if (!using_bigint && has_bigint
&& !Number.isSafeInteger(lcm)) {
using_bigint = true;
number0 = BigInt(number0);
lcm0 = BigInt(lcm0);
lcm = BigInt(number + remainder) + BigInt(lcm0);
number = BigInt(number);
}
} else {
// lcm0 整除 number: 取 number 即可.
lcm = number;
break;
}
}
}
}
return lcm;
};
_// JSDT:_module_
.
/**
* 求多個數之 LCM(Least Common Multiple, 最小公倍數): method 2.<br />
* Using 類輾轉相除法.<br />
*
* @param {Integers}number_array
* number array
*
* @returns {Integer} LCM of the numbers specified
*/
LCM2 = function LCM2(number_array) {
if (arguments.length > 1) {
// Array.from()
number_array = Array.prototype.slice.call(arguments);
}
var i = 0, l = number_array.length, lcm = 1, r, n, num, gcd;
for (; i < l && lcm; i++) {
// 每個數字都要做運算,雖可確保正確,但沒有效率!
if (!isNaN(num = n = Math.abs(parseInt(number_array[i])))) {
gcd = lcm;
// Euclidean algorithm.
while (r = n % gcd)
n = gcd, gcd = r;
lcm = num / gcd * lcm;
}
}
return lcm;
};
/**
* Get <a href="https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm"
* accessdate="2013/8/3 19:45">Extended Euclidean algorithm</a><br />
*
* @param {Integer}n1
* number 1
* @param {Integer}n2
* number 2
* @returns [ GCD, m1, m2 ]: GCD = m1 * n1 + m2 * n2
*
* @see division_with_remainder() @ data.math
* @since 2013/8/3 20:24:30
*/
function extended_GCD(n1, n2) {
var remainder, quotient, using_g1 = false, using_bigint,
// 前一group [dividend 應乘的倍數, divisor 應乘的倍數]
m1g1 = 1, m2g1 = 0;
if (typeof n1 === 'bigint' || typeof n2 === 'bigint') {
// convert all numbers to the same type.
n1 = BigInt(n1);
n2 = BigInt(n2);
m1g1 = BigInt(m1g1);
m2g1 = BigInt(m2g1);
using_bigint = true;
}
// 前前group [dividend 應乘的倍數, divisor 應乘的倍數]
var m1g2 = /* 0 */m2g1, m2g2 = /* 1 */m1g1;
while (remainder = n1 % n2) {
quotient = (n1 - remainder) / n2;
if (!using_bigint) {
// assert: typeof quotient === 'number'
quotient = Math.floor(quotient);
}
// 現 group = remainder = 前前group - quotient * 前一group
if (using_g1 = !using_g1)
m1g1 -= quotient * m1g2, m2g1 -= quotient * m2g2;
else
m1g2 -= quotient * m1g1, m2g2 -= quotient * m2g1;
// swap numbers
n1 = n2;
n2 = remainder;
}
return using_g1 ? [ n2, m1g1, m2g1 ] : [ n2, m1g2, m2g2 ];
}
// extended GCD algorithm
_.EGCD = extended_GCD;
/**
* 帶餘除法 Euclidean division。<br />
* 除非設定 closest否則預設 remainder ≥ 0.
*
* @param {Number}dividend
* 被除數。
* @param {Number}divisor
* 除數。
* @param {Boolean}[closest]
* get the closest quotient
*
* @returns {Array} [ {Integer}quotient 商, {Number}remainder 餘數 ]
*
* @see http://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
* @see extended_GCD() @ data.math
*
* @since 2015/10/31 10:4:45
*/
function division_with_remainder(dividend, divisor, closest) {
if (false)
return [ Math.floor(dividend / divisor),
// 轉正。保證餘數值非負數。
(dividend % divisor + divisor) % divisor ];
var remainder = dividend % divisor;
if (closest) {
if (remainder != 0
// 0 !== 0n
&& Math.absolute(remainder + remainder) > Math.absolute(divisor))
if (remainder < 0)
remainder += Math.absolute(divisor);
else
remainder -= Math.absolute(divisor);
} else if (remainder < 0) {
// assert: (-0 < 0) === false
remainder += Math.absolute(divisor);
}
dividend = (dividend - remainder) / divisor;
if (typeof dividend === 'number') {
dividend = Math.round(dividend);
} else {
// assert: typeof dividend === 'bigint'
}
return [ dividend, remainder ];
}
// 帶餘數除法 division with remainder
_.division = division_with_remainder;
/**
* 取得所有分母為 denominator分子分母互質的循環小數的循環節位數。<br />
* Repeating decimal: get period (repetend length)
*
* @param {Natural}denominator
* 分母
* @param {Boolean}with_transient
* 亦取得非循環節部分位數
* @param {Natural}min
* 必須最小長度,在測試大量數字時使用。若發現長度必小於 min 則即時跳出。效果不俗 (test
* Euler_26(1e7))。
*
* @returns {Array}[{Number}period length 循環節位數 < denominator,
* {Number}transient 非循環節部分位數 ]
*
* @see https://en.wikipedia.org/wiki/Repeating_decimal#Reciprocals_of_composite_integers_coprime_to_10
*/
function period_length(denominator, with_transient, min) {
// 去除所有 2 或 5 的因子。
var non_repeating = 0, non_repeating_5 = 0;
while (denominator % 5 === 0)
denominator /= 5, non_repeating_5++;
while (denominator % 2 === 0)
denominator /= 2, non_repeating++;
if (non_repeating < non_repeating_5)
non_repeating = non_repeating_5;
if (denominator === 1 || denominator <= min)
return with_transient ? [ 0, non_repeating ] : 0;
for (var length = 1, remainder = 1;; length++) {
remainder = remainder * DEFAULT_BASE % denominator;
if (remainder === 1)
return with_transient ? [ length, non_repeating ] : length;
}
}
_.period_length = period_length;
// ---------------------------------------------------------------------//
/**
* 從數集 set 中挑出某些數,使其積最接近指定的數 target。<br />
* To picks some numbers from set, so the product is approximately the
* target number.
*
* TODO: improve/optimize
*
* @param {Array}set
* number set of {Natural}
* @param {Natural}target
* target number
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Array}某些數,其積最接近 target。
*
* @see http://stackoverflow.com/questions/19572043/given-a-target-sum-and-a-set-of-integers-find-the-closest-subset-of-numbers-tha
*/
function closest_product(set, target, options) {
var status, minor_data;
if (Array.isArray(options)) {
status = options;
minor_data = status[0];
options = minor_data.options;
} else {
// 初始化
if (!options)
options = new Boolean;
else if (typeof options === 'number')
options = {
direction : options
};
else if (typeof options === 'boolean')
options = {
sorted : options
};
minor_data = [ Infinity ];
minor_data.options = options;
// status = [ [minor, set of minor], product, set of product ]
status = [ minor_data, ZERO_EXPONENT, [] ];
if (!options.sorted)
set = set.clone()
// 由小至大排序。
.sort(library_namespace.ascending);
}
// direction = -1: 僅接受小於 target 的積。
// direction = +1: 僅接受大於 target 的積。
var direction = options.direction,
//
product = status[1], selected = status[2];
set.some(function(natural, index) {
if (selected[index])
// 已經處理過,跳過。
return;
var _product = product * natural, _selected,
/** {Number}差 ≥ 0 */
difference = Math.abs(target - _product),
// 是否發現新極小值。採用 minor_data 而不 cache 是因為此間 minor_data 可能已經改變。
_status = difference <= minor_data[0];
library_namespace.debug(target + '=' + (target - _product) + '+'
+ natural + '×' + product + ', ' + product + '='
+ (set.filter(function(n, index) {
return selected[index];
}).join('⋅') || 1), 6, 'closest_product');
if (target < _product) {
library_namespace.debug('target < _product, direction: '
+ direction, 6, 'closest_product');
if (!_status || direction < 0) {
library_namespace.debug('積已經過大,之後不會有合適的。', 5,
'closest_product');
return true;
}
}
_selected = selected.clone();
_selected[index] = true;
if (_status && (!(direction > 0) || target <= _product)) {
_status = set.filter(function(n, index) {
return _selected[index];
}).join(closest_product.separator);
if (difference === minor_data[0]) {
if (minor_data.includes(_status)) {
library_namespace.debug('已經處理過相同的,跳過。', 5,
'closest_product');
return;
}
minor_data.push(_status);
} else {
minor_data.clear();
minor_data.push(difference, _status);
}
library_namespace.debug('發現極小值:' + target + '=' + difference
+ '+' + natural + '×' + product + ', ' + product + '='
+ (set.filter(function(n, index) {
return selected[index];
}).join('⋅') || 1), 3, 'closest_product');
}
_status = [ minor_data, _product, _selected ];
library_namespace.debug('繼續探究是否有更小的差:' + _status.join(';'), 4,
'closest_product');
closest_product(set, target, _status);
});
return minor_data.length > 1 && minor_data;
}
closest_product.separator = MULTIPLICATION_SIGN;
_.closest_product = closest_product;
// TODO:將數列分為積最接近的兩組。
/**
* Get <a
* href="https://en.wikipedia.org/wiki/Modular_multiplicative_inverse"
* accessdate="2013/8/3 20:10">modular multiplicative inverse</a> (模反元素)
*
* TODO:<br />
* untested!
*
* @param {Integer}number
* number
* @param {Integer}modulo
* modulo
*
* @returns {Integer} modular multiplicative inverse
*
* @since 2013/8/3 20:24:30
*/
function modular_inverse(number, modulo) {
number = extended_GCD(number, modulo);
if (number[0] == 1)
return (number = number[1]) < 0 ? number + modulo : number;
}
_.modular_inverse = modular_inverse;
// factorial_cache[ n ] = n!
// factorial_cache = [ 0! = 1, 1!, 2!, ... ]
var factorial_cache = [ 1 ], factorial_cache_to;
/**
* Get the factorial (階乘) of (natural).<br />
*
* @param {ℕ⁰:Natural+0}natural
* safe integer. 018
*
* @returns {Natural}natural的階乘.
*
* @see https://en.wikipedia.org/wiki/Factorial
*/
function factorial(natural) {
var length = factorial_cache.length;
if (length <= natural && !factorial_cache_to) {
var f = factorial_cache[--length];
while (length++ < natural)
if (isFinite(f *= length))
factorial_cache.push(f);
else {
factorial_cache_to = length - 1;
break;
}
}
return natural < length ? factorial_cache[natural] : Infinity;
}
// var factorial_map = CeL.math.factorial.map(9);
// generate factorial map
factorial.map = function(natural) {
if (!natural)
natural = 9;
if (factorial_cache.length <= natural && !factorial_cache_to)
factorial(natural);
return factorial_cache.slice(0, natural + 1);
};
_.factorial = factorial;
// ---------------------------------------------------------------------//
/**
* http://www.math.umbc.edu/~campbell/NumbThy/Class/Programming/JavaScript.html
* http://aoki2.si.gunma-u.ac.jp/JavaScript/
*/
/**
* 得到開方數,相當於 Math.floor(Math.sqrt(number)) === Math.sqrt(number) | 0. get
* integer square root. TODO: use 牛頓法
*
* @param {Number}
* positive number
*
* @return r, r^2 ≤ number < (r+1)^2
*
* @see <a href="http://www.azillionmonkeys.com/qed/sqroot.html"
* accessdate="2010/3/11 18:37">Paul Hsieh's Square Root page</a><br />
* <a
* href="http://www.embeddedrelated.com/usenet/embedded/show/114789-1.php"
* accessdate="2010/3/11 18:34">Suitable Integer Square Root Algorithm
* for 32-64-Bit Integers on Inexpensive Microcontroller? |
* Comp.Arch.Embedded | EmbeddedRelated.com</a>
*/
function floor_sqrt(number) {
// return Math.sqrt(number) | 0;
if (!Number.isFinite(number = Math.floor(number)))
return;
var g = 0, v, h, t;
while ((t = g << 1) < (v = number - g * g)) {
// library_namespace.debug(t + ', ' + v);
h = 1;
while (h * (h + t) <= v)
// 因為型別轉關係,還是保留 << 而不用 *2
h <<= 1;// h *= 2;
g += h >> 1;// h / 2;//
}
if (false)
library_namespace.debug('end: ' + t + ', ' + v);
return g;
}
_.floor_sqrt = floor_sqrt;
// count digits of integer: using .digit_length()
function ceil_log(number, base) {
if (!number)
return 0;
if (!base)
base = DEFAULT_BASE;
// assert: base >= 2, base === (base | 0)
number = Math.abs(number);
// ideal
return Math.ceil(base === 10 ? Math.log10(number) : base === 2 ? Math
.log2(number)
// TODO: base = 2^n
: Math.log(number) / Math.log(base));
// slow... should use multiply by exponents
// Logarithm
var log = 0;
if (number < ZERO_EXPONENT) {
while (number < ZERO_EXPONENT) {
number *= base;
if (false)
library_namespace.debug(number);
log--;
}
if (number !== ZERO_EXPONENT)
// 修正。
log++;
} else {
while (number > ZERO_EXPONENT) {
// 因為可能損失 base^exp + (...) 之剩餘部分,因此不能僅採用 Math.floor(number /
// base)
// 但如此較費時。
number /= base;
if (false)
library_namespace.log(number);
log++;
}
}
return log;
}
// add binding
_.ceil_log = ceil_log;
/** {Object}all possible last 2 digits of square number */
var square_ending = Object.create(null);
[ 0, 1, 4, 9, 16, 21, 24, 25, 29, 36, 41, 44, 49, 56, 61, 64, 69, 76, 81,
84, 89, 96 ].forEach(function(n) {
square_ending[n] = null;
});
// Squarity testing
// 檢測 ({Natural}number) 是否為完全平方數
// a square number or perfect square. TODO: use 牛頓法
// is square number, n²
function is_square(number) {
// 快速判定 possible_square(number)
// https://www.johndcook.com/blog/2008/11/17/fast-way-to-test-whether-a-number-is-a-square/
// 0x0213 = parseInt('1111110111101100', 2).toString(0x10)
if (0xFDEC & (1 << (number & 0xF))) {
return false;
}
// TRUE only if number % 16 === 0, 1, 4, 9
// %16 有4個: http://oeis.org/A023105
if (!((number % 100) in square_ending)) {
return false;
}
number = Math.sqrt(number);
return number === (number | 0) && number;
// another method
// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots
// https://gmplib.org/manual/Perfect-Square-Algorithm.html
var sqrt = floor_sqrt(number);
return sqrt * sqrt === number && sqrt;
}
_.is_square = is_square;
// 檢測 ({Natural}f1 * {Natural}f2) 是否為完全平方數
function product_is_square(f1, f2) {
if (f1 === f2) {
return true;
// e.g., r * p^2, r * p^2
}
// e.g., p^2 * r, q^2 * r
var product = f1 * f2;
if (Number.isSafeInteger(product)) {
return is_square(product);
}
// 除法不比較快。
if (f1 > f2) {
// swap
var tmp = f1;
f1 = f2;
f2 = tmp;
}
if (!Number.isSafeInteger(f2)) {
library_namespace.error('The number ' + f2
+ ' is NOT a safe number!');
}
// assert: f1 < f2
if (f2 % f1 === 0) {
// e.g., r, r * p^2
return is_square(f2 / f1);
}
if (is_square(f1)) {
// e.g., p^2, q^2
return is_square(f2);
}
if (is_square(f2)) {
return false;
}
for (var index = 0, length = Math.min(10, primes.length); index < length; index++) {
var p = primes[index], p2 = p * p;
tmp = false;
while (f1 % p2 === 0) {
f1 /= p2;
tmp = true;
}
while (f2 % p2 === 0) {
f2 /= p2;
tmp = true;
}
if (f1 % p === 0) {
while (f1 % p === 0 && f2 % p === 0) {
f1 /= p;
f2 /= p;
tmp = true;
}
}
if (tmp) {
product = f1 * f2;
if (Number.isSafeInteger(product)) {
return is_square(product);
}
}
}
// 找GCD。較慢但沒辦法。
var gcd = GCD(f1, f2);
return is_square(f1 / gcd)
//
&& is_square(f2 / gcd);
}
_.product_is_square = product_is_square;
/**
* <code>
n(n+1)/2=T, n∈, n=?
n(n+1)/2=T, n=?
n = 1/2 (sqrt(8 T+1)-1)
Reduce[(n (1 + n))/2 == T, n]
n = 1/2 (sqrt(8 T+1)-1)
Reduce[n(3n1)/2==P, n]
n = 1/6 (sqrt(24 P+1)+1)
// hexagonal
Reduce[n(2n1)==H, n]
n = 1/4 (sqrt(8 H+1)+1)
</code>
*/
function is_triangular(natural) {
// https://en.wikipedia.org/wiki/Triangular_number
var sqrt = is_square(8 * natural + 1);
return sqrt && sqrt % 2 === 1;
}
_.is_triangular = is_triangular;
function is_generalized_pentagonal(generalized) {
// https://en.wikipedia.org/wiki/Pentagonal_number
return is_square(24 * generalized + 1);
}
_.is_generalized_pentagonal = is_generalized_pentagonal;
function is_pentagonal(natural) {
// https://en.wikipedia.org/wiki/Pentagonal_number
var sqrt = is_square(24 * natural + 1);
return sqrt && sqrt % 6 === 5;
}
_.is_pentagonal = is_pentagonal;
// 素勾股數 primitive Pythagorean triple
var 素勾股數 = [], last_Pythagorean_m = 2;
/**
* primitive Pythagorean triples 素勾股數組/素商高數組/素畢氏三元數
*
* @param {Natural}limit
* limit of m. 若欲改成 limit of 斜邊,請輸入斜邊長後自行 filter。
*
* @returns {Array}primitive Pythagorean triple list
*
* @see https://en.wikipedia.org/wiki/Pythagorean_triple#Generating_a_triple
* The triple generated by Euclid's formula is primitive if and only if
* m and n are coprime and m n is odd.
*/
function Pythagorean_list(limit) {
if (last_Pythagorean_m < limit) {
for (var m = last_Pythagorean_m; m < limit; m++) {
for (var m2 = m * m, m_2 = 2 * m, n, n2 = n = m % 2 === 0 ? 1
: 0;
// 設 m > n 互質且均是正整數m 和 n 有一個是偶數,
// 計算出來的 (a, b, c) 就是素勾股數。所有素勾股數可用列式找出
n < m; n += 2, n2 = n * n) {
if (GCD(m, n) === 1) {
var a = m2 - n2, b = m_2 * n, c = m2 + n2;
// let a < b < c
素勾股數.push(a < b ? [ a, b, c ] : [ b, a, c ]);
}
}
}
last_Pythagorean_m = limit;
}
return 素勾股數;
}
_.Pythagorean_list = Pythagorean_list;
// ---------------------------------------------------------------------//
// Catalan_number[0] = 1
var Catalan_number_list = [ 1 ];
// Catalan numbers
// @see https://en.wikipedia.org/wiki/Catalan_number
function Catalan_number(NO) {
if (NO < Catalan_number_list.length) {
// use cache
return Catalan_number_list[NO];
}
var n = Catalan_number_list.length - 1,
//
this_Catalan_number = Catalan_number_list[n];
for (; n < NO; n++) {
this_Catalan_number = this_Catalan_number * (4 * n + 2) / (n + 2);
Catalan_number_list.push(this_Catalan_number);
}
return this_Catalan_number;
}
_.Catalan_number_list = Catalan_number_list;
_.Catalan_number = Catalan_number;
// ---------------------------------------------------------------------//
/** {Array}Collatz_conjecture_steps[number] = steps. cache 以加快速度。 */
var Collatz_conjecture_steps_cache = [ , 1 ];
if (false) {
// 此法費時 1.5 倍, 12s → 19s
Collatz_conjecture_steps_cache = new Array(1000001);
Collatz_conjecture_steps_cache[1] = 1;
}
// assert: Collatz_conjecture_steps_cache[1] === 1 (因程式判別方法需要此項)
// Collatz conjecture
// https://en.wikipedia.org/wiki/Collatz_conjecture
function Collatz_conjecture(natural) {
if (!(natural > 0))
return;
var chain = [ natural ];
while (natural > 1) {
chain.push(natural % 2 === 0 ? natural /= 2
: (natural = natural * 3 + 1));
}
// Collatz_conjecture_steps_cache[natural] = chain.length;
// return all terms
return chain;
}
// 為計算 steps 特殊化。
// assert: CeL.Collatz_conjecture.steps(natural) ===
// CeL.Collatz_conjecture(natural).length
function Collatz_conjecture_steps(natural) {
if (!(natural > 0))
return;
var chain = [];
while (!(natural in Collatz_conjecture_steps_cache)) {
chain.push(natural);
if (natural % 2 === 0)
natural /= 2;
else
natural = natural * 3 + 1;
}
var steps = Collatz_conjecture_steps_cache[natural] + chain.length, s = steps;
// 紀錄 steps。
chain.forEach(function(natural) {
Collatz_conjecture_steps_cache[natural] = s--;
});
return steps;
}
/**
* <code>
backwards 反向:
1000000: 153 steps
999999: 259 steps
999667: 290 steps
999295: 396 steps
997823: 440 steps
970599: 458 steps
939497: 507 steps
837799: 525 steps
</code>
*/
// search the longest chain / sequence below ((natural))
function Collatz_conjecture_longest(natural) {
if (!(natural > 0))
return;
// maximum steps
var max_steps = 0, max_steps_natural;
// brute force
for (var n = 1, steps, _n; n <= natural; n++) {
if (n in Collatz_conjecture_steps_cache)
steps = Collatz_conjecture_steps_cache[_n = n];
else {
steps = Collatz_conjecture_steps(_n = n);
// 預先快速處理所有 2倍數字。採用此方法約可增加 5% 速度。不採用此方法n 正反向速度差不多。
while (_n * 2 <= natural) {
Collatz_conjecture_steps_cache[_n *= 2] = ++steps;
}
}
if (max_steps < steps) {
library_namespace.debug(natural + ': ' + steps + ' steps', 3,
'Collatz_conjecture.longest');
max_steps = steps;
max_steps_natural = _n;
}
}
return [ max_steps_natural, max_steps ];
}
_.Collatz_conjecture = Collatz_conjecture;
Collatz_conjecture.steps = Collatz_conjecture_steps;
Collatz_conjecture.longest = Collatz_conjecture_longest;
// ---------------------------------------------------------------------//
// https://en.wikipedia.org/wiki/Memoization
/** {Array}質數列表。 cache / memoization 以加快速度。 */
var primes = [ 2, 3, 5 ],
/**
* last prime tested.<br />
* assert: last_prime_tested is ((6n ± 1)). 因此最起碼應該從 5 開始。
*
* @type {Natural}
*/
last_prime_tested = primes.at(-1);
// https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
// the sieve of Eratosthenes 篩法
function prime_sieve(limit, limit_index) {
// var list = _.number_array(limit + 1, 0, Int8Array);
var list = new Array(limit + 1);
// 重建 re-build list (table)
primes.forEach(function(prime) {
for (var number = prime; number <= limit;) {
// list[number += prime] = 1;
list[number += prime] = true;
}
});
for (var n = last_prime_tested; n <= limit;) {
if (list[++n])
continue;
// n is prime
// library_namespace.debug(n + ' is prime');
primes.push(n);
if (limit_index && primes.length > limit_index)
break;
// 登記所有倍數。
for (var number = n; number <= limit;) {
// list[number += prime] = 1;
list[number += prime] = true;
}
}
last_prime_tested = primes.at(-1);
return primes;
}
_.prime_sieve = prime_sieve;
// integer: number to test
function test_is_prime(integer, index, sqrt) {
// assert: Number.isInteger(integer), integer ≥ 0
index |= 0;
if (!sqrt)
sqrt = floor_sqrt(integer);
// 採用試除法, use trial division。
// 從第一個質數一直除到 ≤ sqrt(integer) 之質數
for (var prime, length = primes.length; index < length;) {
if (integer % (prime = primes[index++]) === 0)
// return: prime factor found
return integer === prime ? false : prime;
if (sqrt < prime)
return false;
}
// 質數列表中的質數尚無法檢測 integer。
}
/**
* Get the prime[index] or prime list.
*
* @param {Natural}[index]
* prime index starts from 1
* @param {Natural}[limit]
* the upper boundary of prime value
*
* @returns {Natural}prime value
*/
function prime(index, limit) {
if (!(index > 0)) {
if (limit > 0) {
index = prime_pi(limit);
return primes.slice(0, index);
}
return primes;
}
if (primes.length < index) {
if (false && index - primes.length > 1e6) {
// using the sieve of Eratosthenes 篩法
// 沒比較快。
// 詳細數量應採 prime π(x)。
// https://zh.wikipedia.org/wiki/%E8%B3%AA%E6%95%B8%E5%AE%9A%E7%90%86
prime_sieve(limit || index * 10, index);
} else {
// assert: last_prime_tested is ((6n ± 1))
/**
* {Boolean}p1 === true: last_prime_tested is 6n+1.<br />
* else: last_prime_tested is 6n-1
*/
var p1 = last_prime_tested % 6 === 1;
for (; primes.length < index
&& Number.isSafeInteger(last_prime_tested);) {
last_prime_tested += (p1 = !p1) ? 2 : 4;
// 實質為 https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
if (!test_is_prime(last_prime_tested, 2))
primes.push(last_prime_tested);
if (limit && limit <= last_prime_tested)
break;
}
library_namespace.debug('last prime tested = '
+ last_prime_tested, 2, 'prime');
}
}
return primes[index - 1];
}
_.prime = prime;
// prime #5484598 = 94906249, the biggest prime <
// Math.sqrt(Number.MAX_SAFE_INTEGER) - 1.
// the 2nd biggest prime is 94906247.
// CeL.prime(CeL.prime_pi(Number.MAX_SAFE_INTEGER = 2^53 - 1)) =
// 9007199254740881
function prime_pi(value) {
value = Math.floor(Math.abs(value));
if (last_prime_tested < value)
prime(value, value);
// +1: index of function prime() starts from 1!
return primes.search_sorted(value, true) + 1;
}
_.prime_pi = prime_pi;
/**
* Get the primorial (質數階乘, p_n#) of (NO).<br />
*
* @param {Natural}NO
* safe integer. 113
*
* @returns {Natural}p_NO的質數階乘.
*
* @see https://en.wikipedia.org/wiki/Primorial
*/
function primorial(NO) {
if (!(NO >= 1))
return MULTIPLICATIVE_IDENTITY;
prime(NO);
var index = 0, product = MULTIPLICATIVE_IDENTITY;
while (index < NO)
product *= primes[index++];
return product;
}
/**
* Get the primorial (質數階乘, n#) of (natural).<br />
*
* @param {Natural}natural
* safe integer. 242
*
* @returns {Natural}natural的質數階乘.
*
* @see https://en.wikipedia.org/wiki/Primorial
*/
function primorial_natural(natural) {
// 2: primes[0]
if (!(natural >= 2))
return MULTIPLICATIVE_IDENTITY;
var index = 0, length = prime_pi(natural), product = MULTIPLICATIVE_IDENTITY;
while (index < length)
product *= primes[index++];
return product;
}
_.primorial = primorial;
primorial.natural = primorial_natural;
// return multiplicand × multiplier % modulus
// assert: 三者皆為 natural number, and Number.isSafeInteger() is OK.
// max(multiplicand, multiplier) < modulus. 否則會出現錯誤!
function multiply_modulo(multiplicand, multiplier, modulus) {
var quotient = multiplicand * multiplier;
if (Number.isSafeInteger(quotient))
return quotient % modulus;
// 避免 overflow
if (multiplicand > multiplier)
quotient = multiplicand, multiplicand = multiplier,
multiplier = quotient;
if (quotient === 1)
throw new Error('Please use data.math.integer instead!');
quotient = Math.floor(modulus / multiplicand);
quotient = (multiplicand * (multiplier % quotient) - Math
.floor(multiplier / quotient)
* (modulus % multiplicand))
% modulus;
return quotient;
}
_.multiply_modulo = multiply_modulo;
// return integer ^ exponent % modulus
// assert: 三者皆為 natural number, and Number.isSafeInteger() is OK. 否則會出現錯誤!
function power_modulo(integer, exponent, modulus) {
for (var remainder = 1, power = integer % modulus;;) {
if (exponent % 2 === 1)
remainder = multiply_modulo(remainder, power, modulus);
if ((exponent >>= 1) === 0)
return remainder;
if ((power = multiply_modulo(power, power, modulus)) === 1)
return remainder;
}
}
_.power_modulo = power_modulo;
function power_modulo(natural, exponent, modulus) {
var remainder = 1;
for (natural %= modulus; exponent > 0; natural = natural * natural
% modulus, exponent >>= 1)
if (exponent % 2 === 1)
remainder = remainder * natural % modulus;
return remainder;
}
_.power_modulo = power_modulo;
// MillerRabin primality test
// return true: is composite, undefined: probable prime (PRP) / invalid
// number
// https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
function Miller_Rabin(natural, times) {
if (natural % 2 === 0)
return natural !== 2;
if (!(natural < sqrt_max_integer) || natural < 2)
return;
var n_1 = natural - 1, d = n_1, s = 0, a, x;
do {
s++, d /= 2;
} while (d % 2 === 0);
// assert: s > 0
if (!(times |= 0))
times = 3;
for (var prime_index = 0; prime_index < times;) {
// 3rd 起用大數( > 307) 偵測。
x = power_modulo(prime(++prime_index + (prime_index < 3 ? 0 : 60)),
d, natural);
if (x === 1 || x === n_1)
continue;
var i = 1, j = 1;
for (; i < s; i++) {
x = x * x % natural;
if (x === 1)
return true;
if (x === n_1) {
j = 0;
break;
}
}
if (j)
// composite
return true;
}
// probable prime to base 2, 3.
// probable prime (PRP), probably prime
// https://en.wikipedia.org/wiki/Probable_prime
}
_.Miller_Rabin = Miller_Rabin;
/**
* Test if ((natural)) is not prime. 是否不為質數。
*
* 對大數,僅能確定為合數,不是質數;不能保證是質數。
*
* @param {Natural}natural
* natural number to test
*
* @returns true: is composite.<br />
* false: is prime.<br />
* prime: min prime factor. The least prime divisor of ((natural)).<br />
* undefined: probable prime (PRP) / invalid number.
*/
function not_prime(natural) {
if (!Number.isSafeInteger(natural) || natural < 2)
return true;
var result;
if (false) {
var sqrt = floor_sqrt(natural = result);
result = 0;
while ((result = test_is_prime(natural, result, sqrt)) === undefined) {
// 多取一些質數。
prime((result = primes.length) + 1);
}
}
// warming up. 為 Miller_Rabin() 暖身。
prime(70);
// 先從耗費少的檢測開始。
// 先檢測此數是否在質數列表中。
if (natural <= last_prime_tested)
// -1: NOT_FOUND
return primes.search_sorted(natural) === -1;
result = primes.length < 1e3
&& last_prime_tested * last_prime_tested < natural
// ↑ 1e3: 當有太多質數要測test_is_prime()就不划算了。
? undefined : test_is_prime(natural);
if (result === undefined)
result = Miller_Rabin(natural);
if (result === undefined) {
if (primes.length < 1e5)
// 多取一些質數。一般說來,產生這表的速度頗快。
prime(1e5);
result = test_is_prime(natural);
}
return result;
}
_.not_prime = not_prime;
function Pollards_rho_1980(natural) {
if (natural % 2 === 0)
return 2;
if (natural % 3 === 0)
return 3;
if (!(natural < sqrt_max_integer) || natural < 2)
return;
// reset initial value
// assert: natural > max(y, m, c)
var x = natural - 4, y = 2 + (Math.random() * x | 0), m = 2 + (Math
.random()
* x | 0), c = 2 + (Math.random() * x | 0), r = 1, q = 1, i, k, ys, G;
do {
for (x = y, i = r; i--;)
// f(x) = (x^2 + c) % N
y = (y * y % natural + c) % natural;
k = 0;
do {
for (ys = y, i = Math.min(m, r - k); i--;) {
// f(x) = (x^2 + c) % N
y = (y * y % natural + c) % natural;
q = q * Math.abs(x - y) % natural;
}
G = GCD(q, natural);
k += m;
} while (k < r && G === 1);
r *= 2;
// TODO: 當 r 過大,例如為十位數以上之質數時,過於消耗時間。
} while (G === 1);
if (natural === G)
do {
// f(x) = (x^2 + c) % N
ys = (ys * ys % natural + c) % natural;
G = GCD(Math.abs(x - ys), natural);
} while (G === 1);
return natural === G && G;
}
_.Pollards_rho = Pollards_rho_1980;
// ---------------------------------------------------------------------//
// CeL.factorize(natural > 1).toString(exponentiation_sign,
// multiplication_sign)
function factors_toString(exponentiation_sign, multiplication_sign) {
if (!exponentiation_sign && !Number.prototype.to_super)
exponentiation_sign = true;
if (!multiplication_sign)
// https://en.wikipedia.org/wiki/Multiplication_sign
multiplication_sign = '⋅';
else if (multiplication_sign === true)
// https://en.wikipedia.org/wiki/Interpunct
multiplication_sign = '×';
// others: '*'
var list = [];
for ( var factor in this) {
if (this[factor] > ZERO_EXPONENT)
// expand exponentiation
factor += exponentiation_sign === true ? (multiplication_sign + factor)
.repeat(this[factor] - ZERO_EXPONENT)
// https://en.wikipedia.org/wiki/Exponentiation#In_programming_languages
// exponentiation_sign: ^, **, ↑, ^^, ⋆
: exponentiation_sign ? exponentiation_sign
+ this[factor]
// https://en.wikipedia.org/wiki/Prime_factor
// To shorten prime factorizations, factors are often
// expressed in powers (multiplicities).
: this[factor].to_super();
list.push(factor);
}
return list.join(multiplication_sign);
}
// 計算所有因數個數。
// 計算所有質因數個數: Object.keys(factors).length
// 質因數列表: Object.keys(factors)
function count_all_factors() {
var count = MULTIPLICATIVE_IDENTITY;
for ( var prime in this) {
// exponent
count *= this[prime] + 1;
}
// re-define count
Object.defineProperty(this, 'count', {
enumerable : false,
value : count
});
return count;
}
// 歐拉函數 φ(n), Euler's totient function, Euler's phi function
// 是小於或等於n的正整數中與n互質的數的數目。
// https://en.wikipedia.org/wiki/Euler's_totient_function
function coprime() {
var count = this.natural;
for ( var prime in this) {
count = count / prime * (prime - 1);
}
// re-define coprime
Object.defineProperty(this, 'coprime', {
enumerable : false,
value : count
});
return count;
}
/**
* 取得某數的質因數分解,整數分解/因式分解/素因子分解, prime factorization, get floor factor.<br />
* 唯一分解定理(The Unique Factorization Theorem)告訴我們素因子分解是唯一的,這即是稱為算術基本定理 (The
* Fundamental Theorem of Arithmeric) 的數學金科玉律。<br />
*
* use Object.keys(factors) to get primes
*
* @param {Natural}natural
* integer number ≥ 2
* @param {Natural}radix
* output radix
* @param {Natural}index
* start prime index
*
* @return {Object}prime factors { prime1:power1, prime2:power2, ... }
*
* @requires floor_sqrt
*
* @see <a href="http://homepage2.nifty.com/m_kamada/math/10001.htm"
* accessdate="2010/3/11 18:7">Factorizations of 100...001</a>
*/
function factorize(natural, radix, index, factors) {
if (!Number.isSafeInteger(natural) || natural < 2
/**
* javascript 可以表示的最大整數值 = 10^21-2^16-1 = 999999999999999934463
*
* @see http://www.highdots.com/forums/javascript/how-js-numbers-represented-internally-166538-4.html
*/
// && !(1 < (natural = Math.floor(Math.abs(natural))) && natural <
// 999999999999999934469)
)
return;
if (!radix)
radix = undefined;
try {
radix.toString(radix);
} catch (e) {
// IE8?
radix = DEFAULT_BASE;
}
if (!factors)
factors = Object.create(null);
Object.defineProperties(factors, {
natural : {
enumerable : false,
value : natural
},
toString : {
enumerable : false,
value : factors_toString
},
count : {
enumerable : false,
configurable : true,
get : count_all_factors
},
coprime : {
enumerable : false,
configurable : true,
get : coprime
}
});
index |= 0;
var p = 1, sqrt = floor_sqrt(natural);
for (var power, length = primes.length; p <= sqrt;)
// 採用試除法, use trial division。
if (natural % (p = index < length ? primes[index++]
// find enough primes
: prime(++index)) === 0) {
for (power = 1; (natural /= p) % p === 0;)
power++;
factors[p.toString(radix)] = power;
sqrt = floor_sqrt(natural);
}
if (1 < natural)
factors[natural.toString(radix)] = 1;
return factors;
// 為了獲得確實結果,在 (Number.MAX_SAFE_INTEGER) 範圍內不採用 Pollard's rho。
// 事實上,若加上 (natural < sqrt_max_integer) 的限制,
// 一般說來在此範圍內使用 Pollard's rho 亦不切實際。
if (sqrt < p) {
// assert: natural is now prime.
if (1 < natural)
factors[natural.toString(radix)] = 1;
return factors;
}
var fA = [], fac = function(i) {
if (sqrt_max_integer <= natural || not_prime(i)) {
var p, count = 3;
while (count-- && !(p = Pollards_rho_1980(i)))
;
if (p) {
fac(p);
fac(i / p);
return;
} else
library_namespace
.warn('factorize: 無法分解'
+ (library_namespace.is_debug()
&& Miller_Rabin(i) ? '合數' : '')
+ '因子 [' + i.toString()
+ '];您或許有必要自行質因數分解此數!');
}
fA.push(i);
};
fac(natural);
if (Array.isArray(factors)) {
// TODO
}
fA.sort(library_namespace.ascending);
fA.forEach(function(p) {
p = p.toString(radix);
if (p in factors)
factors[p]++;
else
factors[p] = 1;
});
return factors;
}
factorize._toString = factors_toString;
_.factorize = factorize;
// test
(function() {
function count(n) {
var a = factorize(n), s = '', v = 1;
if (a) {
for ( var i in a) {
s += '*' + i + (a[i] > 1 ? '^' + a[i] : '');
v *= Math.pow(i, a[i]);
}
s = s.substr(1) + '=' + v + '=' + n;
} else
s = 'error! ' + n;
document.getElementById('result').value += s
+ '\n-------------------------------------------\n';
}
});
/**
* 得到第一個質數因子。
*
* @param {Natural}natural
* natural number ≥ 2
*
* @returns{Natural}the first prime factor of ((natural))
*/
function first_factor(natural) {
for (var p = 1, sqrt = floor_sqrt(natural), index = 0, length = primes.length; p <= sqrt;)
// 採用試除法, use trial division。
if (natural % (p = index < length ? primes[index++]
// find enough primes
: prime(++index)) === 0)
return p;
return natural;
}
_.first_factor = first_factor;
/**
* Get the summation map 1limit of proper factors.
*
* A proper factor of a positive integer n is a factor of n other than 1 or
* n (Derbyshire 2004, p. 32).<br />
* A positive divisor of n which is different from n is called a proper
* divisor or an aliquot part of n.
*
* @param {Natural}limit
* 處理到哪個數字。include limit itself.
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Natural}summation of proper factors
*
* @see http://mathworld.wolfram.com/ProperFactor.html
*/
function factor_sum_map(limit, options) {
var add_1, add_self,
// default: {Natural}∑ summation of proper factors
get_sum = true, processor, list;
if (options) {
if (typeof options === 'function')
processor = options.processor;
add_1 = options.add_1;
add_self = options.add_self;
list = options.list;
if (options.all_factors)
add_1 = add_self = true;
processor = options.processor;
if (typeof processor === 'string' && factor_sum_map[processor])
processor = factor_sum_map[processor];
if (typeof processor !== 'function') {
processor = undefined;
// options.list: get factor list instead of summation.
get_sum = !options.get_list;
}
}
// assert: limit ≥ 1
// ++limit: number up to ((limit)), but need ((limit+1)) elements.
++limit;
var
// ((index)) starts from 2.
// skip 0: needless, natural numbers starts from 1.
// skip 1: already precessed by .fill(1).
index = list ? 0 : 2,
// options.add_1: every number has factor 1,
// set this if you want include 1 into sum.
factor_map = get_sum ? _.number_array(limit, add_1 ? 1 : 0,
options.type)
//
: add_1 ? [ , [ 1 ] ] : [];
if (false)
// factor_map[0] is nonsense 無意義,預設成 0。
factor_map[0] = 0;
if (options && typeof options.preprocessor === 'function')
factor_map = options.preprocessor(factor_map) || factor_map;
// generate factor map: a kind of sieve method 篩法.
// https://en.wikipedia.org/wiki/Sieve_theory
for (;; index++) {
var number = list ? list[index] : index;
if (!(number < limit))
break;
for (var n = add_self ? number : 2 * number; n < limit; n += number) {
// 處理所有 ((number)) 之倍數。
if (processor)
// processor : function(factor_map, factor, natural) {;}
processor(factor_map, number, n);
else if (get_sum)
// 將所有 ((number)) 之倍數都加上 ((number))。
// Append ((number)) to every multiple of ((number)).
//
// factor_map[0] is nonsense 無意義
// factor_map[number>0]
// = summation of the proper factors of number.
factor_map[n] += number;
else if (n in factor_map)
factor_map[n].push(number);
else
factor_map[n] = add_1 ? [ 1, number ] : [ number ];
}
}
library_namespace.debug('factor map: [' + factor_map.length + '] '
+ factor_map.slice(0, 30).join(';') + '...', 1,
'factor_sum_map');
return factor_map;
}
_.factor_sum_map = factor_sum_map;
// count factors
factor_sum_map.count = function(factor_map, factor, natural) {
factor_map[natural]++;
};
/**
* count coprime numbers below ((limit)).<br />
* 計算所有比各數字小,並與各數字互質的數。<br />
* 歐拉函數 φ(n), Euler's totient function, Euler's phi function
* 是小於或等於n的正整數中與n互質的數的數目。
*
* @param {Natural}limit
* natural number ≥ 2
*
* @returns {Array}coprime map
*
* @see function coprime()<br />
* https://en.wikipedia.org/wiki/Euler's_totient_function
*
*/
function coprime_map(limit, options) {
return factor_sum_map(limit, {
add_self : true,
// 初始化。
preprocessor : function(factor_map) {
factor_map.forEach(function(v, i) {
factor_map[i] = i;
});
},
list : prime(0, limit),
processor : function(factor_map, prime, number) {
factor_map[number] = factor_map[number] / prime * (prime - 1);
}
});
}
_.coprime_map = coprime_map;
/**
* Get perfect number list.
*
* @param {Natural}limit
* 處理到哪個數字。include limit itself.
* @param {Number}type
* type>0: abundant number.<br />
* type<0: deficient number.<br />
* default: perfect number
*
* @returns {Array}number list
*/
function perfect_numbers(limit, type) {
var numbers = [], factor_map = factor_sum_map(limit, {
add_1 : true
});
// skip 0: needless, natural numbers starts from 1.
// 僅對過剩數才需要做此處置。
if (type > 0)
factor_map[0] = 0;
// 雖然設定 add_1但對 1 本身,應該為 0。
factor_map[1] = 0;
factor_map.forEach(type > 0 ? function(factor_sum, index) {
// abundant number or excessive number. 過剩數又稱作豐數或盈數
// https://en.wikipedia.org/wiki/Abundant_number
if (factor_sum > index)
numbers.push(index);
} : type < 0 ? function(factor_sum, index) {
// deficient or deficient number. 虧數又稱作缺數
// https://en.wikipedia.org/wiki/Deficient_number
if (factor_sum < index)
numbers.push(index);
} : function(factor_sum, index) {
// perfect numbers. 完全數,又稱完美數或完備數
// https://en.wikipedia.org/wiki/Perfect_number
if (factor_sum === index)
numbers.push(index);
});
library_namespace.debug('numbers: [' + numbers.length + '] '
+ numbers.slice(0, 30) + '...', 1, 'perfect_numbers');
return numbers;
}
_.perfect_numbers = perfect_numbers;
// ---------------------------------------------------------------------//
// 回文數 palindromic number or numeral palindrome
// http://www.csie.ntnu.edu.tw/~u91029/Palindrome.html
function palindrome_list(limit, base) {
if (!base)
base = DEFAULT_BASE;
// 個位數皆為回文數。
var list = new Array(Math.min(base, limit)).fill(ABSORBING_ELEMENT)
.map(function(v, i) {
return i;
});
if (limit <= base)
return list;
for (var power = 1, next_power = base, n;;) {
// 2e 位數(e), e.g., 1001
// left: 10^e10^(e+1)-1
for (var l = power; l < next_power; l++) {
var left = l.toString(base),
// right side
right = left.split('').reverse().join('');
n = parseInt(left + right, base);
if (n >= limit)
break;
list.push(n);
}
if (n >= limit)
break;
// 2e+1 位數, e.g., 10201
// left: 10^e10^(e+1)-1
for (var l = power; l < next_power; l++) {
var left = l.toString(base),
// right side
right = left.split('').reverse().join('');
for (var middle = 0; middle < base; middle++) {
n = parseInt(left + middle + right, base);
if (n >= limit)
break;
list.push(n);
}
}
if (n >= limit)
break;
power = next_power;
next_power *= base;
}
return list;
}
_.palindrome_list = palindrome_list;
function is_palindrome(natural, base) {
if (typeof natural !== 'srting')
// assert: typeof natural !== 'number'
natural = natural.toString(base);
// 將這個數的數字按相反的順序重新排列後,所得到的數和原來的數一樣。
return natural === natural.split('').reverse().join('');
}
_.is_palindrome = is_palindrome;
// ---------------------------------------------------------------------//
_// JSDT:_module_
.
/**
* 猜測一個數可能的次方數。
*
* @param {Number}
* number 數字
* @param {Boolean}
* type false: base 為整數, true: base 為有理數
*
* @returns [{Integer} base 分子, {Integer} base 分母, {Integer} exponent 分子,
* {Integer} exponent 分母]
*
* @since 2005/2/18 19:20 未完成
*/
guess_exponent = function(number, type) {
var bn, bd, en = 1, ed, sq = [ 1, number ], t, q,
// default error, accuracy, stopping tolerance, 容許誤差
error = Number.EPSILON,
//
g = function(n) {
q = _.to_rational_number(n, 99999);
if ((!type || q[1] === 1) && !(q[0] > 99999 && q[1] > 99999)
&& q[2] / n < error)
bn = q[0], bd = q[1], ed = t;
};
if (!ed)
g(sq[t = 1]);
if (!ed)
g(sq[t = 2] = sq[1] * sq[1]);
if (!ed)
g(sq[t = 3] = sq[1] * sq[2]);
if (!ed)
g(sq[t = 4] = sq[2] * sq[2]);
if (!ed)
g(sq[t = 5] = sq[2] * sq[3]);
if (!ed)
bn = number, bd = ed = 1;
return [ bn, bd, en, ed ];
};
/**
* get random prime(s)
*
* @param {Integer}count
* 個數
* @param {Array}exclude
* 排除
* @param {Boolean}all_different
*
* @returns random prime / random prime array
*
* @since 2009/10/21 11:57:47
*/
function get_random_prime(count, exclude, all_different) {
var i, j, p = [], l;
if (!count || count < 1)
count = 1;
if (!get_random_prime.excluded)
get_random_prime.excluded = [];
if (exclude)
exclude = [];
// 先行準備好足夠的 primes。
prime(2 * count, 2 * count);
for (j = 0; j < count; j++) {
// timeout
l = 80;
do {
i = Math.round(10 * Math.tan(Math.random() * 1.5));
if (!--l)
// timeout
return;
} while (get_random_prime.excluded[i]);
p.push(primes[i]);
if (exclude)
exclude.push(i);
}
// 選完才排除本次選的
if (exclude)
for (j = 0, l = exclude.length; j < l; j++) {
i = exclude[j];
if (get_random_prime.excluded[i])
get_random_prime.excluded[i]++;
else
get_random_prime.excluded[i] = 1;
}
return count === 1 ? p[0] : p;
}
// return [GCD, n1, n2, ..]
get_random_prime.get_different_number_set = function(count, till, GCD_till) {
delete this.excluded;
if (!GCD_till)
GCD_till = 1e5;
if (!till)
till = 1e5;
/**
* 求乘積, 乘到比till小就回傳.
*
* @param nums
* num array
* @param till
* @returns {Number}
*/
function get_product(nums, till) {
var p = 1, i = 0, l = nums.length;
for (; i < l; i++) {
if (till && p * nums[i] > till)
break;
p *= nums[i];
}
return p;
}
var GCD = get_product(this(20, 1), GCD_till), na = [], n_e = [], n, i = 0, out;
n_e[GCD] = 1;
for (; i < count; i++) {
out = 80; // timeout
do {
n = this(20);
n.unshift(GCD);
n = get_product(n, till);
} while (n_e[n] && --out);
n_e[n] = 1;
na.push(n);
}
if (typeof lcm == 'function')
na.LCM = lcm(na);
na.GCD = GCD;
return na;
};
// ------------------------------------------------------------------------------------------------------//
/**
* 求取反函數 caculator[-1](result)
*
* @deprecated
*/
function get_boundary(caculator, result, down, up, limit) {
if (up - down === 0)
return up;
var boundary, value, increase;
// assert: caculator(down) caculator(up) 為嚴格遞增/嚴格遞減函數。
if (caculator(up) - caculator(down) < 0) {
// swap.
boundary = up;
up = down;
down = boundary;
}
// assert: caculator(down) < caculator(up)
increase = down < up;
if (!(limit > 0))
limit = 800;
do {
boundary = (up + down) / 2;
// console.log(down + ' ' + boundary + ' ' + up);
if (boundary === down || boundary === up)
return boundary;
value = result - caculator(boundary);
if (value === 0) {
if (result - caculator(down) === 0) {
down = boundary;
value = true;
}
if (result - caculator(up) === 0) {
up = boundary;
value = true;
}
if (value && (increase ? up - down > 0 : up - down < 0))
continue;
return boundary;
}
if (value > 0)
down = boundary;
else
up = boundary;
} while (--limit > 0 && (increase ? up - down > 0 : up - down < 0));
throw 'get_boundary: caculator is not either strictly increasing or decreasing?';
}
/**
* 求根/求取反函數 equation^-1(y)。 using Secant method.
*
* @param {Function}equation
* 演算式, mapping function
* @param {Number}x0
* 內插法(線性插值 Interpolation)求值之自變數 variable 下限,設定初始近似值。
* @param {Number}x1
* 內插法(線性插值 Interpolation)求值之自變數 variable 上限,設定初始近似值。
* @param {Number}[y]
* 目標值。default: 0. get (equation^-1)(y)
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Number}root: equation(root)≈y
*
* @see Interpolation 以內插值替換
* https://en.wikipedia.org/wiki/Root-finding_algorithm
* https://en.wikipedia.org/wiki/Secant_method
*/
function secant_method(equation, x0, x1, y, options) {
// default error, accuracy, stopping tolerance, 容許誤差
var error = Number.EPSILON;
if (!options)
options = Object.create(null);
else if (options > 0)
error = options;
else if (options.error > 0)
error = Math.abs(options.error);
y = +y || 0;
var count = (options.count || 40) | 0,
// assert: y0 = equation(x0)
y0 = 'y0' in options ? options.y0 : equation(x0),
// assert: y1 = equation(x1)
y1 = 'y1' in options ? options.y1 : equation(x1),
//
x2 = x1, y2 = y1;
if (typeof options.start_OK === 'function'
// 初始測試: Invalid initial value, 不合理的初始值,因此毋須繼續。
&& !options.start_OK(y0, y1))
return;
// main loop
while (error < Math.abs(y2 - y) && count-- > 0
// 分母不應為 0 或 NaN。
&& (y0 -= y1)
// 測試已達極限,已經得到相當好的效果。無法取得更精準值。
// assert: else: x0===x, 可能是因為誤差已過小。
&& ((x2 = x1 - (x1 - x0) * (y - y1) / y0) !== x1 || x1 !== x0)) {
// evaluate result
y2 = equation(x2);
if (false)
library_namespace.debug(count + ': ' + x2 + ',' + y2 + ' → '
+ (y2 - y));
// shift items
x0 = x1, y0 = y1;
x1 = x2, y1 = y2;
}
return x2;
}
_.secant_method = secant_method;
/**
* 求根/求取反函數 equation^-1(y)。 using Sidi's generalized secant method.
*
* @param {Function}equation
* 演算式, mapping function
* @param {Number}x0
* 求值之自變數 variable 下限,設定初始近似值。
* @param {Number}x1
* 求值之自變數 variable 上限,設定初始近似值。
* @param {Number}[y]
* 目標值。default: 0. get (equation^-1)(y)
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Number}root: equation(root)≈y
*
* @see https://en.wikipedia.org/wiki/Root-finding_algorithm
* @see https://en.wikipedia.org/wiki/Sidi's_generalized_secant_method
*/
function Sidi_method(equation, x0, x1, y, options) {
// default error, accuracy, stopping tolerance, 容許誤差
var error = Number.EPSILON;
if (!options)
options = Object.create(null);
else if (typeof options === 'number' && options > 0)
// ↑ @Firefox/44.0:
// isNaN(Object.create(null)): TypeError: can't convert v to number
// Number.isNaN(Object.create(null)) === false
error = options;
else if (options.error > 0)
error = Math.abs(options.error);
y = +y || 0;
var count = (options.count || 40) | 0,
// assert: y0 = equation(x0)
y0 = 'y0' in options ? options.y0 : equation(x0),
// assert: y1 = equation(x1)
y1 = 'y1' in options ? options.y1 : equation(x1);
if (typeof options.start_OK === 'function'
// 初始測試: Invalid initial value, 不合理的初始值,因此毋須繼續。
&& !options.start_OK(y0, y1))
return;
// initialization
var x2 = x1 - (x1 - x0) * (y1 - y) / (y1 - y0),
//
y2 = equation(x2), x3 = x2, y3 = y2,
// divided differences, 1階差商
y10 = (y1 - y0) / (x1 - x0), y21 = (y2 - y1) / (x2 - x1),
// 2階差商
y210 = (y21 - y10) / (x2 - x0),
// 暫時使用。
denominator;
// main loop of Sidi's generalized secant method (take k = 2)
while (error < Math.abs(y3 - y)
&& count-- > 0
// 檢查是否兩個差距極小的不同輸入,獲得相同輸出。
&& y21 !== 0
// 分母不應為 0 或 NaN。
&& (denominator = y21 + y210 * (x2 - x1))
// Avram Sidi (2008), "Generalization Of The Secant Method For
// Nonlinear Equations"
// 可能需要考量會不會有循環的問題。
&& ((x3 = x2 - (y2 - y) / denominator) !== x2 || x2 !== x1 || x1 !== x0)) {
// evaluate result
y3 = equation(x3);
if (false)
console.log(count + ': ' + x3 + ',' + y3 + ' → error '
+ (y3 - y));
// shift items
x0 = x1, y0 = y1;
x1 = x2, y1 = y2;
x2 = x3, y2 = y3;
// reckon divided differences
y10 = y21;
y21 = (y2 - y1) / (x2 - x1);
// y210 = (y21 - y10) / (x2 - x0);
// incase y21 === y10
if (y210 = y21 - y10)
y210 /= x2 - x0;
if (false)
console.log('divided differences: ' + [ y10, y21, y210 ]);
}
return x3;
}
_.Sidi_method = Sidi_method;
/**
* 以 Brent's method 求根/求取反函數 equation^-1(y)。
*
* TODO: 牛頓法, options.derivative
*
* @param {Function}equation
* 演算式, mapping function
* @param {Number}x0
* 求值之自變數 variable 下限,設定初始近似值。
* @param {Number}x1
* 求值之自變數 variable 上限,設定初始近似值。
* @param {Number}[y]
* 目標值。default: 0. get (equation^-1)(y)
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Number}root: equation(root)≈y
*
* @see https://en.wikipedia.org/wiki/Root-finding_algorithm
* @see https://en.wikipedia.org/wiki/Brent%27s_method
* @see http://www.boost.org/doc/libs/1_58_0/boost/math/tools/minima.hpp
* @see http://people.sc.fsu.edu/~jburkardt/cpp_src/brent/brent.cpp
* @see http://www.cscjournals.org/manuscript/Journals/IJEA/volume4/Issue1/IJEA-33.pdf
* @see http://www.cscjournals.org/manuscript/Journals/IJEA/volume2/Issue1/IJEA-7.pdf
*/
function Brent_method(equation, x0, x1, y, options) {
// default error, accuracy, stopping tolerance, 容許誤差
// @see Number.EPSILON
var error = 0;
if (!options)
options = Object.create(null);
else if (typeof options === 'number' && options > 0)
error = options;
else if (options.error > 0)
error = Math.abs(options.error);
y = +y || 0;
var count = (options.count || 40) | 0,
// assert: y0 = equation(x0)
y0 = 'y0' in options ? options.y0 : equation(x0),
// assert: y1 = equation(x1)
y1 = 'y1' in options ? options.y1 : equation(x1);
if (typeof options.start_OK === 'function'
// 初始測試: Invalid initial value, 不合理的初始值,因此毋須繼續。
&& !options.start_OK(y0, y1))
return;
if (y0 === y)
return x0;
if (y1 === y)
return x1;
if (!((y0 - y) * (y1 - y) < 0))
// the root is not bracketed.
// 但亦有可能只是所取範圍過大,若中間多挑幾點,或許能找到合適的值。
// 回退至使用 Sidi's generalized secant method。
return Sidi_method(equation, x0, x1, y, options);
// copy from double zero () @
// http://people.sc.fsu.edu/~jburkardt/cpp_src/brent/brent.cpp
// rewrite when I have time...
var a = x0, b = x1, c, d, e, sa, sb, sc, fa, fb, fc, m, tol, p, q, r, s;
//
// Make local copies of A and B.
//
c = sa = a;
fc = fa = y0 - y;
sb = b;
fb = y1 - y;
d = e = b - a;
while (true) {
if (Math.abs(fc) < Math.abs(fb)) {
sa = sb, sb = c, c = sa;
fa = fb, fb = fc, fc = fa;
}
// tol = 2 * Number.EPSILON * Math.abs(sb) + error;
tol = 0;
if (sa === sb || Math.abs(m = (c - sb) / 2) <= tol || fb === 0) {
break;
}
if (Math.abs(e) < tol || Math.abs(fa) <= Math.abs(fb)) {
d = e = m;
} else {
s = fb / fa;
if (sa === c) {
p = 2 * m * s;
q = 1 - s;
} else {
q = fa / fc;
r = fb / fc;
p = s * (2 * m * q * (q - r) - (sb - sa) * (r - 1));
q = (q - 1) * (r - 1) * (s - 1);
}
if (0 < p) {
q = -q;
} else {
p = -p;
}
s = e;
e = d;
if (2 * p < 3 * m * q - Math.abs(tol * q)
&& p < Math.abs(0.5 * s * q)) {
d = p / q;
} else {
d = e = m;
}
}
sa = sb;
fa = fb;
if (tol < Math.abs(d)) {
sb = sb + d;
} else if (0 < m) {
sb = sb + tol;
} else {
sb = sb - tol;
}
fb = equation(sb) - y;
if ((0 < fb && 0 < fc) || (fb <= 0 && fc <= 0)) {
c = sa;
fc = fa;
d = e = sb - sa;
}
}
return sb;
}
_.Brent_method = Brent_method;
_.find_root = Brent_method;
/**
* 不用微分求取局部極小值 get local minimum using Brent's golden section search
*
* 注意: 不同的輸入,即使對同一極值,亦可能會得出不同的輸出值。
*
* @param {Function}equation
* 演算式, mapping function
* @param {Number}min
* 求值之自變數 variable 下限,設定初始近似值。
* @param {Number}max
* maximum. 求值之自變數 variable 上限,設定初始近似值。
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Number}[x,fx]: equation(x)≈fx≈minimum
*
* @see https://en.wikipedia.org/wiki/Maxima_and_minima
* https://en.wikipedia.org/wiki/Golden_section_search
* https://zh.wikipedia.org/wiki/%E6%9E%81%E5%80%BC#.E6.B1.82.E6.9E.81.E5.80.BC.E7.9A.84.E6.96.B9.E6.B3.95
* https://zh.wikipedia.org/wiki/%E5%8F%98%E5%88%86%E6%B3%95
* http://maths-people.anu.edu.au/~brent/pub/pub011.html
* http://math.stackexchange.com/questions/58787/looking-for-numerical-methods-for-finding-local-maxima-and-minima-of-a-function
*/
function Brent_minima(equation, min, max, options) {
// default error, accuracy, stopping tolerance, 容許誤差
// @see Number.EPSILON
var error = 0;
if (!options)
options = Object.create(null);
else if (options > 0)
error = options;
else if (options.error > 0)
error = Math.abs(options.error);
var count = (options.count || 40) | 0, data, data_u;
// copy from std::pair<T, T> brent_find_minima @
// http://www.boost.org/doc/libs/1_58_0/boost/math/tools/minima.hpp
// rewrite when I have time...
var tolerance = error,
// minima so far
x,
// second best point
w,
// previous value of w
v,
// most recent evaluation point
u,
// The distance moved in the last step
delta,
// The distance moved in the step before last
delta2,
// function evaluations at u, v, w, x
fu, fv, fw, fx,
// midpoint of ((min)) and ((max))
mid,
// minimal relative movement in x
fract1, fract2,
// golden ratio, don't need too much precision here!
golden = 0.3819660;
x = w = v = max;
fx = equation(x);
if (Array.isArray(fx))
data = fx[1], fx = fx[0];
fw = fv = fx;
delta2 = delta = 0;
do {
// get midpoint
mid = (min + max) / 2;
// work out if we're done already:
fract1 = tolerance * Math.abs(x) + tolerance / 4;
fract2 = 2 * fract1;
if (Math.abs(x - mid) <= (fract2 - (max - min) / 2))
break;
if (Math.abs(delta2) > fract1) {
// try and construct a parabolic fit:
var r = (x - w) * (fx - fv), q = (x - v) * (fx - fw), p = (x - v)
* q - (x - w) * r;
q = 2 * (q - r);
if (q > 0)
p = -p;
q = Math.abs(q);
var td = delta2;
delta2 = delta;
// determine whether a parabolic step is acceptible or not:
if ((Math.abs(p) >= Math.abs(q * td / 2))
|| (p <= q * (min - x)) || (p >= q * (max - x))) {
// nope, try golden section instead
delta2 = (x >= mid) ? min - x : max - x;
delta = golden * delta2;
} else {
// whew, parabolic fit:
delta = p / q;
u = x + delta;
if (((u - min) < fract2) || ((max - u) < fract2))
delta = (mid - x) < 0 ? -Math.abs(fract1) : Math
.abs(fract1);
}
} else {
// golden section:
delta2 = (x >= mid) ? min - x : max - x;
delta = golden * delta2;
}
// update current position:
u = (Math.abs(delta) >= fract1) ? (x + delta)
: (delta > 0 ? (x + Math.abs(fract1)) : (x - Math
.abs(fract1)));
fu = equation(u);
if (Array.isArray(fu))
data_u = fu[1], fu = fu[0];
if (fu <= fx) {
// good new point is an improvement!
// update brackets:
if (u >= x)
min = x;
else
max = x;
if (x === u)
// 無改變。
break;
// update control points:
v = w, w = x, x = u;
fv = fw, fw = fx, fx = fu;
data = data_u;
} else {
// Oh dear, point u is worse than what we have already,
// even so it *must* be better than one of our endpoints:
if (u < x)
min = u;
else
max = u;
if ((fu <= fw) || (w == x)) {
// however it is at least second best:
v = w, w = u;
fv = fw, fw = fu;
} else if ((fu <= fv) || (v == x) || (v == w)) {
// third best:
v = u;
fv = fu;
}
}
} while (--count);
(x = new Number(x)).y = fx;
if (data !== undefined)
if (library_namespace.is_Object(data))
Object.assign(x, data);
else
x.data = data;
return x;
}
_.Brent_minima = Brent_minima;
// 不用微分求取求取局部極小值 get local minimum
_.find_minima = Brent_minima;
_.find_maxima = function(equation, min, max, options) {
return Brent_minima(function(x) {
return -equation(x);
}, min, max, options);
};
// 不用微分求取局部極值 get local maximum and minimum
// _.find_extremum = Brent_minima;
// ------------------------------------------------------------------------------------------------------//
// 組合數學反向思考: 有重複的=全-沒有重複的
// 解法之所以錯誤往往是因為重複計數。
// 裝載問題
// http://codex.wiki/post/117994-555
// 正整數拆分/數字拆解演算法:將數字拆分成最大元素不大於 max 的組合
// http://www.nowamagic.net/algorithm/algorithm_IntegerDivisionDynamicProgramming.php
// https://openhome.cc/Gossip/AlgorithmGossip/SeparateNumber.htm
// https://en.wikipedia.org/wiki/Knapsack_problem
/**
* Get the count of integer partitions. 整數分拆: 將正整數 sum 拆分,表達成一些正整數的和。
*
* TODO: part
*
* @param {Natural}sum
* integer to be apart.
* @param {Natural|Array}[part_count]
* TODO: 給出恰好劃分成 part_count 個整數的劃分。
* @param {Array}[summands]
* 給出只包括 summands 的劃分。
* @param {Array}cache
* cache[sum][summands] = count
*
* @returns {Natural}組合方法數
*
* @see 貪心算法,貪心法 https://en.wikipedia.org/wiki/Greedy_algorithm
*
* @inner
*/
function count_partitions(sum, part_count, summands, cache) {
var key = summands.join(''), _c = cache[sum];
// https://en.wikipedia.org/wiki/Memoization
if (!_c)
_c = cache[sum] = [];
else if (key in _c)
return _c[key];
library_namespace.debug([ sum, summands ], 3);
// 不更動 summands
summands = summands.slice();
var summand = summands.pop(), count = sum / summand | 0;
sum %= summand;
if (summands.length === 0) {
// 檢查當前解是否是可行解。
// 若有餘數,表示此法不通。
// e.g., 以2元分3元
return sum === 0 ? 1 : 0;
}
var counter = 0;
for (; count >= 0; count--, sum += summand) {
library_namespace.debug(summand + '⋅' + count + '+' + sum + '; '
+ counter + '; ' + summands, 3);
if (sum === 0) {
// e.g., 以 5元分100元當count===20時此時也算一次。
counter++;
} else {
counter += count_partitions(sum, part_count, summands, cache);
}
}
return _c[key] = counter;
}
/**
* Get the count of integer partitions. 整數分拆: 將正整數 sum 拆分,表達成一些正整數的和。
* 兌換/分桶/分配問題
*
* TODO: part, count of summands, options
*
* TODO: 部分應該有 O(1) 的方法。 see
* http://www.mobile01.com/topicdetail.php?f=37&t=2195318&p=3
*
* @param {Natural}sum
* integer to be apart.
* @param {Natural|Array}[part_count]
* TODO: 給出恰好劃分成 part_count 個整數的劃分。
* @param {Array}[summands]
* 給出只包括 summands 的劃分。
*
* @returns {Natural}組合方法數
*
* @see https://en.wikipedia.org/wiki/Partition_%28number_theory%29
* 組合數學/總價值固定之錢幣排列組合方法數<br />
* http://www.cnblogs.com/python27/p/3303721.html
* http://mathworld.wolfram.com/Partition.html
* http://mathworld.wolfram.com/PartitionFunctionP.html
* http://www.zhihu.com/question/21075235
* http://blog.csdn.net/iheng_scau/article/details/8170669
*/
function integer_partitions(sum, part_count, summands) {
// assert: sum≥0
if (!sum)
return 0;
if (!summands || !summands.length) {
if (part_count)
throw 'integer_partitions: NYI';
if (sum < 0)
// p(n) = 0 for n negative.
return 0;
// the partition function p(n)
// https://en.wikipedia.org/wiki/Partition_%28number_theory%29#Partitions_in_a_rectangle_and_Gaussian_binomial_coefficients
var count = _.number_array(sum + 1);
// p(0) = 1
count[0] = 1;
for (var i = 1; i <= sum; i++)
for (var j = i; j <= sum; j++)
count[j] += count[j - i];
library_namespace.debug(count, 3);
return count[sum];
}
// 檢查是否有解。
if (sum % GCD.apply(null, summands) !== 0)
return;
summands = summands.slice().sort(library_namespace.ascending);
return count_partitions(sum, part_count, summands, []);
}
_.integer_partitions = integer_partitions;
// ------------------------------------------------------------------------------------------------------//
_// JSDT:_module_
.
/**
* VBScript has a Hex() function but JScript does not.
*
* @param {Number}number
* number
*
* @return {String} number in hex
*
* @example alert('0x'+CeL.hex(16725))
*/
hex = function(number) {
return ((number = Number(number)) < 0 ? number + 0x100000000 : number)
.toString(16);
};
_// JSDT:_module_
.
/**
* 補數計算。 正數的補數即為自身。若要求得互補之後的數字,請設成負數。
*
* @param {Number}number
* number
*
* @return {Number} base 1: 1's Complement, 2: 2's Complement, (TODO: 3, 4,
* ..)
* @example alert(complement())
* @see http://www.tomzap.com/notes/DigitalSystemsEngEE316/1sAnd2sComplement.pdf
* https://en.wikipedia.org/wiki/Method_of_complements
* https://en.wikipedia.org/wiki/Signed_number_representations
* @since 2010/3/12 23:47:52
*/
complement = function() {
return this.from.apply(this, arguments);
};
_// JSDT:_module_
.complement.prototype = {
base : 2,
// 1,2,..
bits : 8,
// radix complement or diminished radix complement.
// https://en.wikipedia.org/wiki/Method_of_complements
diminished : 0,
/**
* 正負符號. 正: 0/false, 負 negative value:!=0 / true
*/
sign : 0,
// get the value
valueOf : function() {
return this.sign ? -this.value : this.value;
},
/**
* set value
*/
set : function(value) {
var m = Number(value), a = Math.abs(m);
if (isNaN(m) || m && a < 1e-8 || a > 1e12) {
library_namespace.debug('complement.set: error number: '
+ value);
return;
}
this.sign = m < 0;
// this.value 僅有正值
this.value = a;
return this;
},
/**
* input
*/
from : function(number, base, diminished) {
// 正規化
number = ('' + (number || 0)).replace(/\s+$|^[\s0]+/g, '') || '0';
if (false)
library_namespace.debug(number + ':' + number.length + ','
+ this.bits);
// 整數部分位數
var value = number.indexOf('.'), tmp;
// -1: NOT_FOUND
if (value == -1)
value = number.length;
// TODO: not good, need to optimize
if (value > this.bits)
// throw 'overflow';
library_namespace.error('complement.from: overflow: ' + value);
if (typeof diminished === 'undefined')
// illegal setup
diminished = this.diminished;
else
this.diminished = diminished;
if ((base = Math.floor(base)) && base > 0) {
if (base === 1)
base = 2, this.diminished = 1;
this.base = base;
} else
// illegal base
base = this.base;
if (false)
library_namespace.debug(base + "'s Complement");
// TODO: 僅對 integer 有效
value = parseInt(number, base);
tmp = Math.pow(base, this.bits - 1);
if (value >= tmp * base)
// throw 'overflow';
library_namespace.error('complement.from: overflow: ' + value);
// library_namespace.debug('compare ' + value + ',' + tmp);
if (value < tmp)
this.sign = 0;
else {
library_namespace.debug('負數 '
+ (tmp * base - (diminished ? 1 : 0)) + '-' + value
+ '=' + (tmp * base - (diminished ? 1 : 0) - value), 3);
this.sign = 1;
value = tmp * base - (diminished ? 1 : 0) - value;
}
this.value = value;
// library_namespace.debug(number + ' → '+this.valueOf());
return this;
},
/**
* output
*/
to : function(base, diminished) {
if (!(base = Math.floor(base)) || base < 1)
base = this.base;
else if (base === 1)
base = 2, diminished = 1;
if (typeof diminished === 'undefined')
diminished = this.diminished;
var value = this.value, tmp = Math.pow(base, this.bits - 1);
if (value > tmp || value === tmp && (diminished || !this.sign))
// throw 'overflow';
library_namespace.error('complement.to: overflow: '
+ (this.sign ? '-' : '+') + value);
if (this.sign) {
tmp *= base;
if (diminished)
// TODO: 僅對 integer 有效
tmp--;
// library_namespace.debug('負數 ' + value + 'summation=' + tmp);
// 負數,添上兩補數之和
value = tmp - value;
}
if (false)
library_namespace.debug('value: ' + (this.sign ? '-' : '+')
+ value);
value = value.toString(Math.max(2, this.base));
return value;
}
};
_// JSDT:_module_
.complement.prototype.toString = _.complement.prototype.to;
/**
* haversines
*
* @param {Number}θ
* angle (in radians)
*
* @returns {Number}haversines(θ)
*
* @see https://en.wikipedia.org/wiki/Haversine_formula
*/
function hav(θ) {
return (1 - Math.cos(θ)) / 2;
// hav(θ) = sin^2(θ/2)
}
// ---------------------------------------------------------------------//
/**
* 取差集。<br />
* 警告: 若有成員為 {Object},或許先自行處理會比較有效率。
*
* set_1 → set_1 - set_2 <br />
* set_2 → set_2 - set_1
*
* @param {Array|Object}set_1
* set / hash 1
* @param {Array|Object}set_2
* set / hash 2
* @param {Boolean}[clone]
* clone === true 表示先 clone不直接在原物件上寫入結果。但若為 {Array},則*必定*為
* clone!
*
* @returns {Array}[ complement of set_1, complement of set_2 ]
*/
function get_set_complement(set_1, set_2, clone) {
if (!set_1 || !set_2 || typeof set_1 !== 'object'
|| typeof set_2 !== 'object') {
throw new Error('get_set_complement: Invalid type');
}
var hash_1, hash_2;
// 確保 keys 準備好,並把為 hash 的全部轉到 hash_*。
if (!Array.isArray(set_1)) {
hash_1 = clone ? Object.clone(set_1) : set_1;
set_1 = Object.keys(hash_1);
}
if (!Array.isArray(set_2)) {
hash_2 = clone ? Object.clone(set_2) : set_2;
set_2 = Object.keys(hash_2);
}
// assert: set_1, set_2 are {Array}
// 從比較小的來處理比較快。
// 若 set_1 比較短,則看 hash_2 是否存在;若沒 hash_2 則將key指到比較短的 _2。
// 依之後的演算法hash_2 必須存在,又需建造,因此務必為比較短的,因為消耗高。
// 因此若是另一方已經有 hash則直接用之。
var key_is_2 = !(set_1.length > set_2.length ? hash_1 : hash_2);
library_namespace.debug('key_is_2: ' + key_is_2, 3,
'get_set_complement');
if (key_is_2) {
// swap 1, 2
var tmp = set_1;
set_1 = set_2;
set_2 = tmp;
tmp = hash_1;
hash_1 = hash_2;
hash_2 = tmp;
}
var no_hash_2 = !hash_2;
if (no_hash_2) {
library_namespace.debug('建造 hash: 依之後的演算法hash_2 必須存在。', 3,
'get_set_complement');
hash_2 = set_2.to_hash();
}
var resort_1 = [];
set_1.forEach(function(item) {
// assert: item is in _1
// 處理 set_1 之成員為 {Object} 的情況。
// 警告:這邊需要與 Array.prototype.product() 採用相同的 to string 方法。
var key = typeof item === 'object' ? JSON.stringify(item) : item;
if (key in hash_2) {
library_namespace.debug('key 在 _1 + 在 _2: 在兩方都刪掉: ' + key, 5,
'get_set_complement');
delete hash_2[key];
if (hash_1) {
delete hash_1[key];
}
} else {
library_namespace.debug('key 在 _1 不在 _2: 留下 _1 的 key: ' + key,
5, 'get_set_complement');
if (!hash_1) {
resort_1.push(item);
}
// _2 本來就沒有,不動。
}
});
set_1 = resort_1;
if (no_hash_2) {
// 得造出 set_2。
// 維持 set_2 的順序,並避免去掉重複。
var keep_order = true;
if (keep_order) {
var resort_2 = [];
set_2.forEach(function(item) {
// 處理 set_2 之成員為 {Object} 的情況。
// 警告:這邊需要與前面採用相同的 to string 方法。
var key = typeof item === 'object' ? JSON.stringify(item)
: item;
if (key in hash_2) {
resort_2.push(item);
}
});
set_2 = resort_2;
} else {
// 這會比較快,但實際應用上會造成不確定性:不能確定截掉的是哪一個。
set_2 = Object.keys(hash_2);
}
hash_2 = undefined;
}
if (key_is_2) {
// swap 1, 2
var tmp = set_1;
set_1 = set_2;
set_2 = tmp;
tmp = hash_1;
hash_1 = hash_2;
hash_2 = tmp;
}
return [ hash_1 || set_1, hash_2 || set_2 ];
}
_.get_set_complement = get_set_complement;
// ---------------------------------------------------------------------//
// CeL.math.number_array()
if (library_namespace.typed_arrays) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
(_.number_array = function number_array_ArrayBuffer(size, fill, type) {
if (false) {
var buffer = new ArrayBuffer(type.BYTES_PER_ELEMENT * size);
}
// DataView
var array = new (type || _.number_array.default_type)(
// buffer
size);
if (fill)
array.fill(fill);
return array;
})
// should TypedArray. e.g., Int32Array, Uint32Array
.default_type = Int32Array;
} else {
_.number_array = function number_array_Array(size, fill) {
// 經過 .fill() 以定義每個元素,這樣在 .forEach() 時才會遍歷到。
return new Array(size).fill(fill || 0);
};
}
/**
* Create digit value table. 建構出位數值表。
*
* 對一般問題,所要求的,即是以 greedy algorithm (貪心算法,貪心法)遞歸搜索,從
* digit_table[0末位數]各選出一位數值使其總合為0。<br />
* 因為各位數值有其特性,因此可能存有些技巧以降低所需處理之數據量。
*
* @param {Array}initial_value
* 當位數每一位數值應當減去的初始值。
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Array}digit value table
*/
function digit_table(initial_value, options) {
var base = options && options.base || DEFAULT_BASE;
if (typeof initial_value === 'string') {
if (initial_value === 'factorial') {
initial_value = factorial.map(base - 1);
} else {
var matched = initial_value.match(/power[\s:=]*(\d+)/i);
if (matched) {
matched = +matched[1];
// initial_value[digit] = digit^power
initial_value = _.number_array(base).map(
function(id, index) {
return Math.pow(index, matched);
});
}
}
}
// assert: initial_value 之元素皆 {Natural} > 0
library_namespace.debug(initial_value, 3);
/**
* value of each digit.
*
* table[exponent=0(maximum exponent)][digit=09] =<br />
* {Number} digit*base^exponent - digit^exponent
*
* @type {Array} [][]
*/
var table = [],
/**
* accumulated min. 自個位數起累積的最小值。
*
* sum_min[exponent=0(maximum exponent)] =<br />
* ∑自0至(exponent-1)位累積的(digit value之最小值)
*
* @type {Array}
*/
sum_min = [];
table.min = sum_min;
// 準備好 digit_value table, min/maximum value。
for (var exponent = 0,
/** {Natural}power = base^exponent */
power = 1;
// 不需要此項限制,照理來說應該在其之前即已跳出。
// exponent < base
; exponent++) {
/** {Array}value_array[digit=09] = {Number}位數值(digit value) */
var value_array = _.number_array(base),
/** {Number}本位數各數字位數值的最小值不包含0 */
min = Infinity;
// 計算 (base^exponent) 之位數值(digit value),並記錄最小值。
for (var digit = 0; digit < base; digit++) {
var digit_value = value_array[digit] = digit * power
- initial_value[digit];
// 因為0不能當數字頭[0] 不設定極值。
if (digit > 0 && digit_value < min)
min = digit_value;
}
/** {Number}本位數各數字位數值的最小值包含0 */
var min_0 = Math.min(min, value_array[0]);
// console.log(value_array);
if (exponent > 0) {
// 記錄自個位數起之累積之最小值。
var last_min = sum_min[exponent - 1];
min += last_min;
min_0 += last_min;
}
if (min > 0)
// 對 n 位數,數值範圍為 base^(n-1)base^n-1。但位數值和若已經過大
// 代表此位數以上,如第 (n+1) 位數就算每個位數值和都取最小值總和也不可能為0。
break;
sum_min.push(min_0);
table.push(value_array);
power *= base;
}
if (!options || !options.find)
return table;
// ------------------------------------------
// process: 以 greedy algorithm (貪心算法,貪心法)遞歸搜索
/**
* calculate sum of digit values
*/
function calculate_sum(sum, exponent, digits) {
// 測試是否應終結。
if (exponent < 0) {
// 0,1 為當然結果。
if (sum === 0 && (digits = +digits) > min)
result.push(digits);
return;
}
if (sum > 0 && sum + sum_min[exponent] > 0)
// 接下來的總和也不可能為0。
return;
// 遞歸搜索
table[exponent--].forEach(function(v, d) {
calculate_sum(digits || d ? sum + v : sum, exponent,
// 對於 0 開頭者,視做少一位數,同時不計算本位數之 [0]。
digits || d ? digits + d : digits);
});
}
var result = [],
/** {Natural}所得值需要大於此值。 */
min;
if (options && ('min' in options))
min = options.min;
else if (!initial_value.some(function(v, d) {
if (d && v !== d) {
min = d;
return true;
}
}))
min = 0;
calculate_sum(0, table.length - 1, '');
library_namespace.debug(result, 2);
return result;
}
_.digit_table = digit_table;
// ---------------------------------------------------------------------//
// assert: {ℕ⁰:Natural+0}((this))
// fast than String(natural).chars(), natural.toString().chars() or
// natural.toString(base).chars()
// TODO: 處理小數/負數/大數
function Number_digits(base) {
if (!((base |= 0) >= 2))
base = DEFAULT_BASE;
var natural = Math.floor(Math.abs(this)), digits = [];
do {
digits.unshift(natural % base);
} while ((natural = Math.floor(natural / base)) > 0);
return digits;
}
// 數字和, 位數和
// natural.digits(base).sum()
// https://en.wikipedia.org/wiki/Digit_sum
// to get digit root: using natural.digit_sum(base) % base
function Number_digit_sum(base) {
if (!((base |= 0) >= 2))
base = DEFAULT_BASE;
var natural = Math.floor(Math.abs(this)), sum = ABSORBING_ELEMENT;
do {
sum += natural % base;
} while ((natural = Math.floor(natural / base)) > 0);
return sum;
}
// count digits of integer
// = floor(log_10(base)) + 1
// TODO: 測試二分搜尋 [base,base^2,base^3,...] 方法之效率。 e.g., [10,100,100,...]
function Number_digit_length(base) {
if (!((base |= 0) >= 2))
base = DEFAULT_BASE;
return (base === 10 ? Math.log10(this) | 0
//
: base === 2 ? Math.log2(this) | 0
// TODO: base = 2^n
: Math.log(this) / Math.log(base) | 0) + 1;
// slow... should use multiply by exponents, or Math.clz32()
var natural = Math.floor(Math.abs(this)), digits = 0;
do {
digits++;
} while ((natural = Math.floor(natural / base)) > 0);
return digits;
}
// reverse the digits
// assert: {ℕ⁰:Natural+0}((this))
// TODO: 處理小數/負數/大數
function Number_reverse(base) {
if (!base)
base = DEFAULT_BASE;
// ABSORBING_ELEMENT
var natural = +this, reversed = 0;
while (natural > 0) {
reversed = reversed * base + (natural % base);
natural = Math.floor(natural / base);
}
return reversed;
}
function Number_is_palindromic(base) {
return this === this.reverse(base);
}
// for palindromic number or numeral palindrome 迴文數, 回文數
// http://articles.leetcode.com/2012/01/palindrome-number.html
function String_is_palindromic(chars) {
if (!chars)
return false;
for (var index = 0, l_index = chars.length - 1; index < l_index; index++, l_index--)
if (chars.charAt(index) !== chars.charAt(l_index))
return false;
return true;
}
function Number_tail(natural, base) {
// TODO
;
}
// ---------------------------------------------------------------------//
/**
* Test if the array is an arithmetic progression.<br />
* 判斷 ((this)) 是否為等差數列/連續整數。<br />
* O(n)
*
* @param {String}type
* 'integer': arithmetic integers,<br />
* 'consecutive': consecutive integers 連續整數型別, if the array
* contains only consecutive integers / consecutive values;<br />
* 'odd': odd consecutive integers,<br />
* 'even': even consecutive integers
*
* @returns {Boolean}is AP
*
* @see https://simple.wikipedia.org/wiki/Consecutive_integer
*/
function Array_is_AP(type) {
var length = this.length;
if (length <= 1)
return length === 1;
var number = this[1],
/** {Boolean}為奇數或偶數型別。 */
parity = type === 'odd' || type === 'even';
if (parity && number % 2 !== (type === 'odd' ? 1 : 0))
return false;
var difference = number - this[0];
if (type && difference !== (parity ? 2 : type === 'consecutive' ? 1
// 當前只要設定 type皆為整數型別。
: Math.floor(difference)))
return false;
for (var index = 2; index < length; index++)
if (this[index] !== (number += difference))
return false;
return true;
}
/**
* <code>
Sum[m + n ((M - m)/(l - 1)), {n, 0, -1 + l}]
=
l(m+M)/2
Sum[(m + n (M - m)/(l - 1) - b)^2, {n, 0, l - 1}]
=
(l (6 b^2 (l-1)-6 b (l-1) (m+M)+(2 l-1) m^2+2 (l-2) m M+(2 l-1) M^2))/(6 (l-1))
</code>
*/
/**
* Test if the array combines an arithmetic progression.<br />
* 判斷 ((this)) 是否可組成等差數列/連續整數,不計較次序。<br />
* O(n)
*
* @param {String}type
* 'integer': arithmetic integers,<br />
* 'consecutive': consecutive integers 連續整數型別, if the array
* contains only consecutive integers / consecutive values;<br />
* 'odd': odd consecutive integers,<br />
* 'even': even consecutive integers
* @param {Integer}MIN
* acceptable minimum
*
* @returns {Number}difference or {Boolean}false
*
* @see https://simple.wikipedia.org/wiki/Consecutive_integer
*/
function Array_combines_AP(type, MIN) {
var length = this.length;
if (length <= 1)
return length === 1;
var min = Infinity,
// maximum
max = -Infinity,
// ABSORBING_ELEMENT
sum = 0,
// 不能僅由 min/max/sum 即定奪是否等差。
// 但尚未確認如此條件即已充分!!
square_sum = 0, square_base = this[0],
/** {Boolean}為奇數或偶數型別。 */
parity = type === 'odd' || type === 'even';
if (this.some(function(number) {
if (number < min) {
min = number;
if (min < MIN)
return true;
}
if (max < number)
max = number;
if (parity ? max - min > 2 * (length - 1) : type === 'consecutive'
&& max - min > length - 1)
return true;
sum += number;
number -= square_base;
square_sum += number * number;
}))
return false;
if (2 * sum !== (max + min) * length)
return false;
// check sum of square
var min_p_max = min + max, min_m_max = min * max;
if (square_sum !== ((2 * length * (min_p_max * min_p_max - min_m_max)
- min_p_max * min_p_max - 2 * min_m_max)
/ (6 * (length - 1)) - square_base * (min_p_max - square_base))
* length)
return false;
if (parity && min % 2 !== (type === 'odd' ? 1 : 0))
return false;
var difference = (max - min) / (length - 1);
if (type && difference !== (parity ? 2 : type === 'consecutive' ? 1
// 當前只要設定 type皆為整數型別。
: Math.floor(difference)))
return false;
return difference;
}
// ---------------------------------------------------------------------//
// 檢查是否有重複的數字。
// use CeL.PATTERN_duplicated(string) to test if string contains duplicated
// chars.
_.PATTERN_duplicated = /(.).*?\1/;
// all the same characters
_.PATTERN_all_same = /^(.)\1*$/;
// http://en.cppreference.com/w/cpp/algorithm/is_permutation
function Array_is_permutation(sequence_2) {
var sequence_1 = this, last = sequence_1.length;
if (last !== sequence_2.length)
return false;
if (sequence_1 === sequence_2)
return true;
while (last-- > 0 && sequence_1[last] === sequence_2[last])
;
if (last < 0)
return true;
last++;
// assert: sequence_1, sequence_2 有不同。
var start = 0;
while (sequence_1[start] === sequence_2[start])
start++;
// count elements
var processed = new Set();
for (; start < last; start++) {
var element = sequence_1[start];
if (processed.has(element))
// skip element counted
continue;
processed.add(element);
// O(n^2)
var index = start, difference = sequence_2[index++] === element ? 0
: 1;
for (; index < last; index++) {
if (sequence_1[index] === element)
difference++;
if (sequence_2[index] === element)
difference--;
}
if (difference !== 0)
return false;
}
return true;
}
// 純量變數 (scalar variable)
function String_is_permutation(sequence_2) {
var sequence_1 = this, last = sequence_1.length;
if (last !== sequence_2.length)
return false;
// 直接比較較快。
if (sequence_1 === sequence_2)
return true;
// processed elements
var processed = Object.create(null);
for (var start = 0; start < last; start++) {
var element = sequence_1.charAt(start);
if (element in processed)
// skip element counted
continue;
processed[element] = null;
if (sequence_1.count_of(element, start + 1) + 1
// O(n^2)
!== sequence_2.count_of(element)) {
return false;
}
}
return true;
}
/**
* 關於尋找相同排列的數字,亦可採用紀錄各數字和的方法。<br />
* 另外,若數字之數量遠小於計算 .is_permutation() 之工作量,則 cache .sort() 反而會快很多。
*/
function Number_is_permutation(sequence_2) {
return this.digit_sum() === (+sequence_2).digit_sum()
// ↑ 先測試數字和是否相同。
&& String(this).is_permutation(String(sequence_2));
}
// ---------------------------------------------------------------------//
/**
* 按升序/降序排列處理每一序列至最後。<br />
* contain exactly the same digits, but in a different order.
*
* 注意: 不會先做排序!
*
* @param {Function}handler
* 處理 function
* @param {Boolean}descending
* default: ascending (small→big 升序序列為最小排列,降序序列為最大的排列), or will
* be descending (big→small 降序)
* @param {Boolean}inplace
* no clone, do not clone array.
*
* @returns {Array}the last array processed
*/
function Array_for_permutation(handler, descending, inplace) {
var array = inplace ? this : this.clone(), last_index = array.length - 1;
if (last_index >= 0)
handler(array);
if (last_index < 1)
return;
var index;
do {
// 求出下一個按升序排列序列。
// http://en.cppreference.com/w/cpp/algorithm/next_permutation
// http://leonard1853.iteye.com/blog/1450085
// http://www.cplusplus.com/reference/algorithm/next_permutation/
index = last_index;
for (var now = array[index], _next; index > 0;) {
// search [index]=now < [index+1]=_next
_next = now;
now = array[--index];
if (descending ? now > _next : now < _next) {
var later_index = last_index;
// search [index]=now < [later_index]=_next
while (true) {
_next = array[later_index];
if (descending ? _next < now : _next > now)
break;
later_index--;
}
// swap [index]=now, [later_index]=_next
array[later_index] = now;
array[index] = _next;
// reverse elements 元素: [index+1] to [last_index]
for (index++, later_index = last_index; index < later_index; index++, later_index--) {
_next = array[index];
array[index] = array[later_index];
array[later_index] = _next;
}
if (handler(array))
index = 0;
break;
}
}
} while (index > 0);
return array;
}
function String_for_permutation(handler, descending, sort) {
if (this.length < 2) {
handler(this);
return;
}
var array = this.split('');
if (sort)
typeof sort === 'function' ? array.sort(sort) : array.sort();
return array.for_permutation(function(array) {
return handler(array.join(''));
}, descending, true);
}
function Number_for_permutation(handler, descending, sort, base) {
if (!base)
base = 10;
if (this < base) {
handler(this);
return;
}
var array = this.digits();
if (sort) {
if (typeof sort === 'function')
array.sort(sort);
else
array.sort();
}
return +array.for_permutation(function(array) {
return handler(+array.join(''));
}, descending, true).join('');
}
// ---------------------------------------------------------------------//
// combinatorics 組合數學
/**
* <code>
[1,2,4,8,16,32].for_combination(3,function(s){console.log(s);})
CeL.for_combination(6,3,function(s){console.log(s);})
TODO:
CeL.for_combination(6,3,function(s){console.log(s);},true)
</code>
*/
// 自 elements 中提取出 select 個元素的組合方法。
// 共 C(elements, select) = elements! / select! / (elements-select)! 種組合數量。
// next_combination
// select ((select)) elements, ((select))-selection
function for_combination(elements, select, handler, descending) {
if (!((select |= 0) > 0))
// nothing select
return;
var map;
if (Array.isArray(elements)) {
// elements as map array
map = elements;
elements = map.length;
}
var index = 0,
/** {Array}index array */
selected = [];
// initialization
for (; index < select; index++)
selected.push(index);
while (true) {
// TODO: descending
handler(map ? selected.map(function(index) {
return descending ? map[elements - index] : map[index];
}) : descending ? selected.map(function(index) {
return elements - index - 1;
}).reverse() : selected);
index = 1;
for (; index < select
&& selected[index] === selected[index - 1] + 1; index++)
;
if (++selected[--index] === elements)
break;
--index;
if (selected[index] !== index)
for (; index >= 0; index--)
selected[index] = index;
}
}
_.for_combination = for_combination;
function Array_for_combination(select, handler, descending, inplace) {
return for_combination(inplace ? this : this.clone(), select, handler,
descending);
}
// ---------------------------------------------------------------------//
// export 導出.
_.MULTIPLICATIVE_IDENTITY = MULTIPLICATIVE_IDENTITY;
_.ZERO_EXPONENT = ZERO_EXPONENT;
_.ABSORBING_ELEMENT = ABSORBING_ELEMENT;
_.MULTIPLICATION_SIGN = MULTIPLICATION_SIGN;
library_namespace.set_method(String.prototype, {
is_palindromic : set_bind(String_is_palindromic),
is_permutation : String_is_permutation,
for_permutation : String_for_permutation
});
library_namespace.set_method(String, {
is_palindromic : String_is_palindromic
});
library_namespace.set_method(Number.prototype, {
// division, divided_by
divided : set_bind(division_with_remainder, true),
floor_sqrt : set_bind(floor_sqrt),
ceil_log : set_bind(ceil_log),
digits : Number_digits,
digit_sum : Number_digit_sum,
digit_length : Number_digit_length,
reverse : Number_reverse,
is_palindromic : Number_is_palindromic,
is_permutation : Number_is_permutation,
for_permutation : Number_for_permutation
});
library_namespace.set_method(Array.prototype, {
to_int_or_bigint : array_to_int_or_bigint,
is_AP : Array_is_AP,
combines_AP : Array_combines_AP,
is_permutation : Array_is_permutation,
for_permutation : Array_for_permutation,
for_combination : Array_for_combination
});
library_namespace.set_method(Math, {
hav : hav
});
return (_// JSDT:_module_
);
}