mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-08-29 08:45:58 +02:00
817 lines
26 KiB
JavaScript
817 lines
26 KiB
JavaScript
|
||
/**
|
||
* @name CeL rational number function
|
||
* @fileoverview
|
||
* 本檔案包含了分數/有理數 (rational number)/比例值 (ratio) 的 functions,相當/類似於 BigRational, BigQuotient (numerator and denominator), BigDecimal。<br />
|
||
* 在純 javascript 的環境下,藉由原生計算功能,盡可能提供高效的大數計算。<br />
|
||
*
|
||
* @example
|
||
* <code>
|
||
* CeL.run('data.math.rational');
|
||
* </code>
|
||
*
|
||
* @since 2013/9/8 13:42:58
|
||
* @see
|
||
* https://en.wikipedia.org/wiki/Rational_number
|
||
*/
|
||
|
||
|
||
/*
|
||
TODO:
|
||
以 BigInt() 重寫。
|
||
http://reference.wolfram.com/mathematica/ref/RootApproximant.html
|
||
|
||
規格書:
|
||
|
||
rational = new Rational(numerator, denominator, base);
|
||
|
||
rational = new Rational(10783, 2775);
|
||
rational = new Rational('10783/2775');
|
||
rational = new Rational('3+2458/2775');
|
||
rational = new Rational('3 2458/2775');
|
||
//https://en.wikipedia.org/wiki/Vinculum_(symbol)
|
||
rational = new Rational('3.88¯576');
|
||
//Brackets
|
||
rational = new Rational('3.88(576)');
|
||
|
||
numerator 10783
|
||
denominator 2775
|
||
|
||
integer part 整數部分 == quotient == continued fraction[0]
|
||
fractional part 分數/小數部分 == remainder / denominator
|
||
|
||
mixed fraction 帶分數 == integer part + fractional part
|
||
vulgar fraction 真分數/假分數 ==
|
||
|
||
decimal approximation (numerical value) 無限小數 3.88576576576576576576576576
|
||
//https://en.wikipedia.org/wiki/Overline#Math_and_science
|
||
repeating decimal 循環小數 3.88¯576
|
||
|
||
continued fraction 連分數 == [3; 1, 7, 1, 3, 15, 1, 1, 2]
|
||
|
||
Egyptian fraction expansion 古埃及分數
|
||
|
||
最簡分數(irreducible fraction, reduced proper fraction)
|
||
約分 reduce
|
||
|
||
*/
|
||
|
||
|
||
|
||
'use strict';
|
||
if (typeof CeL === 'function')
|
||
CeL.run(
|
||
{
|
||
name: 'data.math.rational',
|
||
require: 'data.code.compatibility.|data.native.|data.math.integer.',
|
||
no_extend: 'random,compare',
|
||
code: function (library_namespace) {
|
||
|
||
// requiring
|
||
var Integer = library_namespace.data.math.integer,
|
||
// radix point / radix character / decimal mark
|
||
radix_point = Integer.radix_point(),
|
||
|
||
// 為正規 radix。
|
||
valid_radix = Integer.valid_radix,
|
||
DEFAULT_BASE = Integer.DEFAULT_BASE
|
||
|
||
;
|
||
|
||
// ---------------------------------------------------------------------//
|
||
// basic constants. 定義基本常數。
|
||
|
||
var
|
||
|
||
// copy from data.math
|
||
MULTIPLICATIVE_IDENTITY = library_namespace.MULTIPLICATIVE_IDENTITY,
|
||
// copy from data.math
|
||
ZERO_EXPONENT = library_namespace.ZERO_EXPONENT,
|
||
|
||
// copy from data.math.integer.
|
||
|
||
//{Integer}
|
||
KEY_NUMERATOR = 'numerator',
|
||
//{Integer|Undefined}integer > 0
|
||
KEY_DENOMINATOR = 'denominator',
|
||
//{Boolean|Undefined}最簡分數
|
||
KEY_IRREDUCIBLE = 'irreducible',
|
||
|
||
// 應與 parseInt() 一致。
|
||
DEFAULT_RADIX = parseInt('10'),
|
||
|
||
// 可辨認之數字字串。
|
||
// [ full , sign, integer part 整數部分, sign of fractional part 小數部分, numerator, denominator ]
|
||
PATTERN_FRACTION = /([+\-]?)(?:(\d+)([ +\-]))?(\d+)\/(\d+)/,
|
||
// [ full , sign, integer part 整數部分, fractional part 小數部分, repeating decimal 循環小數1, repeating decimal 循環小數2 ]
|
||
PATTERN_DECIMAL = /([+\-]?)(\d*)\.(\d*)(?:¯(\d+)|\((\d+)\))?/
|
||
|
||
;
|
||
|
||
// ---------------------------------------------------------------------//
|
||
// 初始調整並規範基本常數。
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
// 工具函數
|
||
|
||
function do_modified(rational, not_amount) {
|
||
if (!not_amount)
|
||
delete rational[KEY_IRREDUCIBLE];
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
// definition of module integer
|
||
|
||
/**
|
||
* 任意大小、帶正負號的有理數。rational number instance.
|
||
*
|
||
* @example
|
||
* <code>
|
||
* </code>
|
||
*
|
||
* @class Rational 的 constructor
|
||
* @constructor
|
||
*/
|
||
function Rational(number) {
|
||
if (!(this instanceof Rational))
|
||
return 1 === arguments.length && is_Rational(number) ? number : assignment.apply(new Rational, arguments);
|
||
if (arguments.length > 0)
|
||
assignment.apply(this, arguments);
|
||
}
|
||
|
||
// instance public interface -------------------
|
||
|
||
// https://en.wikipedia.org/wiki/Operation_(mathematics)
|
||
var OP_REFERENCE = {
|
||
'+': add,
|
||
'-': subtract,
|
||
'*': multiply,
|
||
'/': divide,
|
||
'^': power,
|
||
'=': assignment,
|
||
'==': compare
|
||
};
|
||
|
||
Object.assign(Rational.prototype, OP_REFERENCE, {
|
||
reduce: reduce,
|
||
// 下面全部皆為 assignment,例如 '+' 實為 '+='。
|
||
assignment: assignment,
|
||
|
||
// add_assignment
|
||
add: add,
|
||
// subtract_assignment
|
||
subtract: subtract,
|
||
// multiply_assignment
|
||
multiply: multiply,
|
||
// divide_assignment
|
||
divide: divide,
|
||
div: divide,
|
||
|
||
power: power,
|
||
pow: power,
|
||
square: square,
|
||
square_root: square_root,
|
||
sqrt: square_root,
|
||
// 至此為 assignment。
|
||
|
||
clone: clone,
|
||
|
||
//https://en.wikipedia.org/wiki/Absolute_value
|
||
abs: function (negative) {
|
||
do_modified(this, true);
|
||
this[KEY_NUMERATOR].abs(negative);
|
||
return this;
|
||
},
|
||
// 變換正負號。
|
||
negate: function () {
|
||
do_modified(this, true);
|
||
this[KEY_NUMERATOR].negate();
|
||
return this;
|
||
},
|
||
is_positive: function () {
|
||
return this.compare(0) > 0;
|
||
},
|
||
is_negative: function () {
|
||
return this[KEY_NUMERATOR].is_negative();
|
||
},
|
||
// 正負符號。
|
||
// https://en.wikipedia.org/wiki/Sign_(mathematics)
|
||
// https://en.wikipedia.org/wiki/Sign_function
|
||
sign: function (negative) {
|
||
return this[KEY_NUMERATOR].sign(negative);
|
||
},
|
||
|
||
to_continued_fraction: to_continued_fraction,
|
||
to_repeating_decimal: to_repeating_decimal,
|
||
integer_part: function () {
|
||
var n = this[KEY_NUMERATOR].clone().expand_exponent();
|
||
if (KEY_DENOMINATOR in this)
|
||
n.divide(this[KEY_DENOMINATOR]);
|
||
return n;
|
||
},
|
||
toPrecision: toPrecision,
|
||
|
||
factorize: factorize,
|
||
log: log,
|
||
|
||
is_0: function (little_natural) {
|
||
return this[KEY_NUMERATOR].is_0(little_natural);
|
||
},
|
||
isFinite: function () {
|
||
return this[KEY_NUMERATOR].isFinite();
|
||
},
|
||
//compare_amount: compare_amount,
|
||
compare: compare,
|
||
equals: function (number) {
|
||
return this.compare(number) === 0;
|
||
},
|
||
// is proper fraction
|
||
is_proper: function () {
|
||
return (KEY_DENOMINATOR in this) && this[KEY_NUMERATOR].compare(this[KEY_DENOMINATOR]) < 0;
|
||
},
|
||
|
||
op: Integer.set_operate(OP_REFERENCE),
|
||
valueOf: valueOf,
|
||
toString: toString
|
||
});
|
||
|
||
// class public interface ---------------------------
|
||
function is_Rational(value) {
|
||
return value instanceof Rational;
|
||
}
|
||
|
||
function Rational_compare(number1, number2) {
|
||
if (typeof number1 === 'number' && typeof number2 === 'number')
|
||
return number1 - number2;
|
||
|
||
if (!is_Rational(number1))
|
||
number1 = new Rational(number1);
|
||
return number1.compare(number2);
|
||
}
|
||
|
||
//get the extreme value (極端值: max/min) of input values
|
||
function extreme(values, get_minima) {
|
||
var index = values.length, extreme_value, value, compare;
|
||
if (!index)
|
||
// ES6: Math.max: If no arguments are given, the result is −∞.
|
||
return get_minima ? Infinity : -Infinity;
|
||
|
||
extreme_value = values[--index];
|
||
while (0 < index--) {
|
||
// WARNING 注意: 當碰上許多大數時,會出現需要多次轉換 extreme_value 成 Rational 的效能低下情形!
|
||
//但若許多數字不同底,而最大的是 String,則可能獲得部分效能。
|
||
if (Number.isNaN(compare = Rational_compare(extreme_value, value = values[index])))
|
||
// ES6: Math.max: If any value is NaN, the result is NaN.
|
||
return NaN;
|
||
|
||
if (get_minima ? compare > 0 : compare < 0)
|
||
extreme_value = value;
|
||
|
||
//依規範,必須掃描一次,確定沒 NaN。不可中途跳出。
|
||
if (false && (get_minima ? compare > 0 : compare < 0)
|
||
//當有改變時才偵測。
|
||
&& typeof (extreme_value = value) === 'number' && !Number.isFinite(extreme_value = value))
|
||
break;
|
||
}
|
||
return extreme_value;
|
||
}
|
||
|
||
// TODO: max
|
||
function random(max) {
|
||
return new Rational(Integer.random(), Integer.random());
|
||
}
|
||
|
||
function is_0(value, little_natural) {
|
||
if (typeof value === 'string')
|
||
value = new Rational(value);
|
||
return value === (little_natural || 0) || (is_Rational(value) ? value[KEY_NUMERATOR] : Integer(value)).is_0(little_natural);
|
||
}
|
||
|
||
Object.assign(Rational, {
|
||
from_continued_fraction: function (sequence, length, base) {
|
||
var convergent = Integer.convergent_of(sequence, length, base);
|
||
return new Rational(convergent[0], convergent[1]);
|
||
},
|
||
|
||
random: random,
|
||
max: function Rational_max() {
|
||
// get max()
|
||
return extreme(arguments);
|
||
},
|
||
min: function Rational_min() {
|
||
// get min()
|
||
return extreme(arguments, true);
|
||
},
|
||
compare: Rational_compare,
|
||
// little_natural: little natural number, e.g., 1
|
||
is_0: is_0,
|
||
|
||
is_Rational: is_Rational
|
||
});
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
|
||
// 因 clone 頗為常用,作特殊處置以增進效率。
|
||
function clone() {
|
||
var rational = new Rational;
|
||
rational[KEY_NUMERATOR] = this[KEY_NUMERATOR].clone();
|
||
if (KEY_DENOMINATOR in this)
|
||
rational[KEY_DENOMINATOR] = this[KEY_DENOMINATOR].clone();
|
||
if (KEY_IRREDUCIBLE in this)
|
||
rational[KEY_IRREDUCIBLE] = this[KEY_IRREDUCIBLE];
|
||
return rational;
|
||
}
|
||
|
||
function assignment(numerator, denominator, base) {
|
||
do_modified(this);
|
||
|
||
var matched, tmp;
|
||
if (is_Rational(numerator)) {
|
||
[KEY_DENOMINATOR, KEY_IRREDUCIBLE].forEach(function (key) {
|
||
if (key in numerator)
|
||
this[key] = numerator[key];
|
||
else
|
||
delete this[key];
|
||
}, this);
|
||
this[KEY_NUMERATOR] = numerator = numerator[KEY_NUMERATOR];
|
||
|
||
} else if (typeof numerator === 'string' && (matched = numerator.match(PATTERN_FRACTION))) {
|
||
//rational = new Rational('10783/2775');
|
||
//rational = new Rational('3+2458/2775');
|
||
//rational = new Rational('3 2458/2775');
|
||
|
||
if (base === undefined && valid_radix(denominator))
|
||
// shift arguments
|
||
base = denominator, denominator = undefined;
|
||
|
||
if (!valid_radix(base))
|
||
base = DEFAULT_RADIX;
|
||
|
||
// [ full , sign, integer part 整數部分, sign of fractional part 小數部分, numerator, denominator ]
|
||
if (matched[3] === '-' && matched[1] !== '-')
|
||
library_namespace.error('assignment: Invalid number sign!');
|
||
|
||
this[KEY_DENOMINATOR] = tmp = new Integer(matched[5], base, DEFAULT_BASE);
|
||
this[KEY_NUMERATOR] = numerator = new Integer(matched[4], base, DEFAULT_BASE);
|
||
if (matched[2])
|
||
numerator.add((new Integer(matched[2], base, DEFAULT_BASE)).multiply(tmp));
|
||
if (matched[1] === '-')
|
||
// 將正負符號擺在 [KEY_NUMERATOR],確保 [KEY_DENOMINATOR] 不為負。
|
||
numerator.negate();
|
||
|
||
} else if (typeof numerator === 'string' && (matched = numerator.match(PATTERN_DECIMAL))) {
|
||
//https://en.wikipedia.org/wiki/Vinculum_(symbol)
|
||
//rational = new Rational('3.88¯576');
|
||
//Brackets
|
||
//rational = new Rational('3.88(576)');
|
||
|
||
if (base === undefined && valid_radix(denominator))
|
||
// shift arguments
|
||
base = denominator, denominator = undefined;
|
||
|
||
if (!valid_radix(base))
|
||
base = DEFAULT_RADIX;
|
||
|
||
// [ full , sign, integer part 整數部分, fractional part 小數部分, repeating decimal 循環小數1, repeating decimal 循環小數2 ]
|
||
// e.g., 1111.222¯33333 → 1111 + (22233333 - 222) / 99999000
|
||
|
||
// 處理完小數部分之 numerator。
|
||
if (matched[4] || (matched[4] = matched[5])) {
|
||
// 有循環節(period)。
|
||
numerator = new Integer(matched[3] + matched[4], base, DEFAULT_BASE);
|
||
if (matched[3])
|
||
numerator.add(new Integer(matched[3], base, DEFAULT_BASE), true);
|
||
tmp = (base - 1).toString(base).repeat(matched[4].length);
|
||
} else {
|
||
// 無循環節(period)。
|
||
numerator = new Integer(matched[3] || 0, base, DEFAULT_BASE);
|
||
tmp = MULTIPLICATIVE_IDENTITY;
|
||
}
|
||
|
||
if (matched[3])
|
||
tmp += '0'.repeat(matched[3].length);
|
||
if (tmp === MULTIPLICATIVE_IDENTITY)
|
||
delete this[KEY_DENOMINATOR];
|
||
else
|
||
// assert: {String} tmp
|
||
this[KEY_DENOMINATOR] = new Integer(tmp, base, DEFAULT_BASE);
|
||
|
||
if (matched[2])
|
||
numerator.add(new Integer(matched[2], base, DEFAULT_BASE).multiply(this[KEY_DENOMINATOR]));
|
||
|
||
if (matched[1] === '-')
|
||
// 將正負符號擺在 [KEY_NUMERATOR],確保 [KEY_DENOMINATOR] 不為負。
|
||
numerator.negate();
|
||
this[KEY_NUMERATOR] = numerator;
|
||
|
||
} else {
|
||
delete this[KEY_DENOMINATOR];
|
||
if ((this[KEY_NUMERATOR] = numerator = new Integer(tmp = numerator, base, DEFAULT_BASE)).isNaN())
|
||
library_namespace.error('assignment: Invalid number: [' + tmp + '].');
|
||
|
||
// 確保不使用 exponent,使 exponent 為 0。
|
||
else if ((tmp = numerator.get_exponent()) > 0)
|
||
numerator.expand_exponent();
|
||
else if (tmp < 0) {
|
||
(this[KEY_DENOMINATOR] = new Integer(numerator.get_base(), numerator.get_base()))
|
||
.power(-tmp);
|
||
numerator.get_exponent(0);
|
||
}
|
||
}
|
||
|
||
if (denominator)
|
||
// rational = new Rational(10783, 2775);
|
||
this.divide(valid_radix(base) && typeof denominator === 'string' ? new Rational(denominator, MULTIPLICATIVE_IDENTITY, base) : denominator);
|
||
|
||
if ((KEY_DENOMINATOR in this)
|
||
&& (!numerator.isFinite()
|
||
|| numerator.is_0()
|
||
// [KEY_DENOMINATOR] 預設即為 MULTIPLICATIVE_IDENTITY。
|
||
|| this[KEY_DENOMINATOR].is_0(MULTIPLICATIVE_IDENTITY)
|
||
))
|
||
delete this[KEY_DENOMINATOR];
|
||
|
||
return this;
|
||
}
|
||
|
||
function reduce() {
|
||
if ((KEY_DENOMINATOR in this) && !this[KEY_IRREDUCIBLE]) {
|
||
var gcd = this[KEY_NUMERATOR].clone().Euclidean_algorithm(this[KEY_DENOMINATOR].clone())[1];
|
||
this[KEY_NUMERATOR].divide(gcd);
|
||
if (this[KEY_DENOMINATOR].equals(gcd))
|
||
delete this[KEY_DENOMINATOR];
|
||
else
|
||
this[KEY_DENOMINATOR].divide(gcd);
|
||
this[KEY_IRREDUCIBLE] = true;
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 測試大小/比大小
|
||
* @param number the number to compare
|
||
* @return {Number} 0:==, <0:<, >0:>
|
||
* @_name _module_.prototype.compare_to
|
||
*/
|
||
function compare(number) {
|
||
if (typeof number === 'string')
|
||
number = new Rational(number);
|
||
if (is_Rational(number) && (KEY_DENOMINATOR in number.reduce()))
|
||
return this.reduce()[KEY_NUMERATOR].clone().multiply(number[KEY_DENOMINATOR])
|
||
//
|
||
.compare(KEY_DENOMINATOR in this ? this[KEY_DENOMINATOR].clone().multiply(number[KEY_NUMERATOR]) : number[KEY_NUMERATOR]);
|
||
|
||
else {
|
||
if (is_Rational(number))
|
||
number = number[KEY_NUMERATOR];
|
||
return this.reduce()[KEY_NUMERATOR]
|
||
//
|
||
.compare(KEY_DENOMINATOR in this ? this[KEY_DENOMINATOR].clone().multiply(number) : number);
|
||
}
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
//四則運算,即加減乘除, + - * / (+-×÷)**[=]
|
||
//https://en.wikipedia.org/wiki/Elementary_arithmetic
|
||
|
||
// 正規化(normalize) operand
|
||
// return is_Integer
|
||
function normalize_operand(operand, rational) {
|
||
// TODO: defferent base
|
||
return Integer.is_Integer(operand) && operand.get_base() === rational[KEY_NUMERATOR].get_base()
|
||
|| is_Rational(operand) && operand[KEY_NUMERATOR].get_base() === rational[KEY_NUMERATOR].get_base()
|
||
? operand
|
||
: new Rational(operand);
|
||
}
|
||
|
||
// Addition 和: addend + addend = sum
|
||
function add(addend, is_subtract) {
|
||
if (!is_0(addend = normalize_operand(addend, this))) {
|
||
// assert: addend != 0.
|
||
|
||
do_modified(this.reduce());
|
||
|
||
addend = Rational(addend);
|
||
|
||
if (is_Rational(addend) && !(KEY_DENOMINATOR in addend.reduce()))
|
||
addend = addend[KEY_NUMERATOR];
|
||
// assert: addend is non-Rational or reduced Rational with denominator.
|
||
|
||
if (is_Rational(addend)
|
||
// 分母相同時,直接相加減分子。
|
||
? (KEY_DENOMINATOR in this) && addend[KEY_DENOMINATOR].equals(this[KEY_DENOMINATOR])
|
||
// 分母相同(=1)時,直接相加減分子。
|
||
: !(KEY_DENOMINATOR in this))
|
||
this[KEY_NUMERATOR].add(is_Rational(addend) ? addend[KEY_DENOMINATOR] : addend, is_subtract);
|
||
// 分母相同,毋須更動。
|
||
|
||
else {
|
||
// n1/d1 ± n2/d2 = (n1d2 ± n2d1)/d1d2
|
||
// assert: d1 != d2
|
||
var denominator_need_multiply;
|
||
if (is_Rational(addend)) {
|
||
// 僅在 (KEY_DENOMINATOR in addend) 時,才須處理分母。
|
||
if (KEY_DENOMINATOR in this)
|
||
//為不干擾 this[KEY_NUMERATOR].add() 之操作,另作 cache。
|
||
denominator_need_multiply = addend[KEY_DENOMINATOR];
|
||
else
|
||
// 為不干擾 addend,另外創建。
|
||
this[KEY_DENOMINATOR] = addend[KEY_DENOMINATOR].clone();
|
||
this[KEY_NUMERATOR].multiply(addend[KEY_DENOMINATOR]);
|
||
addend = addend[KEY_NUMERATOR];
|
||
}
|
||
this[KEY_NUMERATOR].add(KEY_DENOMINATOR in this ? this[KEY_DENOMINATOR].clone().multiply(addend) : addend, is_subtract);
|
||
if (denominator_need_multiply)
|
||
this[KEY_DENOMINATOR].multiply(denominator_need_multiply);
|
||
}
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
// Subtraction 差: minuend − subtrahend = difference
|
||
function subtract(subtrahend) {
|
||
return this.add(subtrahend, true);
|
||
}
|
||
|
||
// Multiplication 乘: multiplicand × multiplier = product
|
||
function multiply(multiplier) {
|
||
if (!this[KEY_NUMERATOR].isNaN())
|
||
if (is_0(multiplier = normalize_operand(multiplier, this))) {
|
||
do_modified(this);
|
||
this[KEY_NUMERATOR].assignment(this[KEY_NUMERATOR].isFinite() ? 0 : NaN);
|
||
delete this[KEY_DENOMINATOR];
|
||
|
||
} else if (!is_0(multiplier, MULTIPLICATIVE_IDENTITY)) {
|
||
do_modified(this.reduce());
|
||
|
||
if (is_Rational(multiplier) && !(KEY_DENOMINATOR in multiplier.reduce()))
|
||
multiplier = multiplier[KEY_NUMERATOR];
|
||
// assert: multiplier is non-Rational or reduced Rational with denominator.
|
||
|
||
if (is_Rational(multiplier)) {
|
||
this[KEY_NUMERATOR].multiply(multiplier[KEY_NUMERATOR]);
|
||
if (KEY_DENOMINATOR in this)
|
||
this[KEY_DENOMINATOR].multiply(multiplier[KEY_DENOMINATOR]);
|
||
else
|
||
this[KEY_DENOMINATOR] = multiplier[KEY_DENOMINATOR].clone();
|
||
} else
|
||
this[KEY_NUMERATOR].multiply(multiplier);
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
// Division 除: dividend ÷ divisor = quotient
|
||
function divide(denominator) {
|
||
if (!this[KEY_NUMERATOR].isNaN())
|
||
if (!denominator || is_0(denominator = normalize_operand(denominator, this))) {
|
||
do_modified(this);
|
||
this[KEY_NUMERATOR].assignment(1 / 0);
|
||
delete this[KEY_DENOMINATOR];
|
||
|
||
} else if (!is_0(denominator, MULTIPLICATIVE_IDENTITY)) {
|
||
do_modified(this.reduce());
|
||
|
||
if (is_Rational(denominator)) {
|
||
if (KEY_DENOMINATOR in denominator.reduce())
|
||
this[KEY_NUMERATOR].multiply(denominator[KEY_DENOMINATOR]);
|
||
denominator = denominator[KEY_NUMERATOR];
|
||
}
|
||
|
||
if (KEY_DENOMINATOR in this)
|
||
this[KEY_DENOMINATOR].multiply(denominator);
|
||
else
|
||
this[KEY_DENOMINATOR] = denominator.clone();
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
|
||
// https://en.wikipedia.org/wiki/Minimal_polynomial_(field_theory)
|
||
// return p[{Integer}], p[0] + p[1]*this = 0
|
||
function minimal_polynomial() {
|
||
this.reduce();
|
||
var polynomial = [this[KEY_NUMERATOR], this[KEY_DENOMINATOR]];
|
||
// translate to {Number} if possible.
|
||
polynomial.forEach(function (coefficient, index) {
|
||
var value = coefficient.valueOf();
|
||
polynomial[index] = Number.isSafeInteger(value) ? value : coefficient.clone();
|
||
});
|
||
return polynomial;
|
||
}
|
||
|
||
function continued_fraction_toString() {
|
||
return '[' + this.join(',').replace(/,/, ';') + ']';
|
||
}
|
||
|
||
function to_continued_fraction() {
|
||
var c = KEY_DENOMINATOR in this
|
||
// http://www.mathpath.org/concepts/cont.frac.htm
|
||
? this[KEY_NUMERATOR].clone().Euclidean_algorithm(this[KEY_DENOMINATOR].clone())[0]
|
||
//
|
||
: [this[KEY_NUMERATOR].clone()];
|
||
Object.defineProperty(c, 'toString', {
|
||
enumerable: false,
|
||
value: continued_fraction_toString
|
||
});
|
||
return c;
|
||
}
|
||
|
||
// precise divide, to repeating decimal
|
||
// radix===1: get {Integer} object instead of {String}
|
||
// return [ integer part, non-repeating fractional part, period (repeating decimal part) ]
|
||
// https://en.wikipedia.org/wiki/Repeating_decimal
|
||
function to_repeating_decimal(radix, period_limit) {
|
||
return this[KEY_NUMERATOR].precise_divide(this[KEY_DENOMINATOR] || MULTIPLICATIVE_IDENTITY, radix, period_limit);
|
||
}
|
||
|
||
// precision: 不包含小數點,共取 precision 位,precision > 0。
|
||
function toPrecision(precision) {
|
||
if (!(0 < precision) || !Number.isSafeInteger(precision |= 0))
|
||
return this.toString();
|
||
|
||
var d, tmp, is_negative = this[KEY_NUMERATOR].is_negative();
|
||
if (precision < 1e2
|
||
// 當循環節(period)相對於 precision 過長,採用 .to_repeating_decimal() 無實益。
|
||
|| ((KEY_DENOMINATOR in this) && precision < 4 * this[KEY_DENOMINATOR].valueOf())
|
||
|| !(d = this.to_repeating_decimal(DEFAULT_RADIX, precision))) {
|
||
//general method
|
||
d = new Integer(this[KEY_NUMERATOR], DEFAULT_RADIX);
|
||
if (KEY_DENOMINATOR in this)
|
||
d.divide(tmp = new Integer(this[KEY_DENOMINATOR], DEFAULT_RADIX),
|
||
precision + tmp.length + Math.max(tmp.get_exponent(), 0));
|
||
d = d.toString();
|
||
d = d.match(/(-?\d*)\.(\d+)/) || d.match(/(-?\d*)/);
|
||
d = [d[1], d[2] || '', ''];
|
||
}
|
||
|
||
// assert: d = [ {String}integer part, {String}non-repeating fractional part, {String}period (repeating decimal part) ]
|
||
|
||
if (d[0].length === precision + (is_negative ? 1 : 0) && d[0].charAt(d[0].length - 1) !== '0')
|
||
return d[0];
|
||
|
||
if (is_negative)
|
||
//減少負擔。
|
||
d[0] = d[0].slice(1);
|
||
|
||
if (d[0] === '0') {
|
||
precision++;
|
||
if (tmp = d[1].match(/^0+/))
|
||
precision += tmp[0].length;
|
||
}
|
||
|
||
if (d[0].length < precision) {
|
||
// 會用到小數部分。
|
||
d[0] += radix_point + d[1];
|
||
// -2: radix_point, 判斷用之位數1位
|
||
if (d[0].length - 2 < precision) {
|
||
if (!d[2])
|
||
d[2] = '0';
|
||
d[0] += d[2].repeat(Math.ceil((precision - d[0].length + 2) / d[2].length));
|
||
}
|
||
// 34.56 3
|
||
d = (5 <= (d[0].charAt(precision + 1) | 0)
|
||
? d[0].slice(0, precision) + ((d[0].charAt(precision) | 0) + 1)
|
||
: d[0].slice(0, precision + 1));
|
||
|
||
} else
|
||
d = d[0].charAt(0) + radix_point
|
||
+ (5 <= (d[0].charAt(precision) | 0)
|
||
? d[0].slice(1, --precision) + ((d[0].charAt(precision) | 0) + 1)
|
||
: d[0].slice(1, precision))
|
||
+ 'e+' + (d[0].length - 1);
|
||
|
||
|
||
return is_negative ? '-' + d : d;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------//
|
||
// advanced functions
|
||
|
||
function factorize(radix, limit) {
|
||
var f, d, factors = this.reduce()[KEY_NUMERATOR].factorize(radix, limit);
|
||
|
||
if (KEY_DENOMINATOR in this) {
|
||
d = this[KEY_DENOMINATOR].factorize(radix, limit);
|
||
// TODO: sort
|
||
for (f in d)
|
||
factors[f] = -d[f];
|
||
}
|
||
|
||
return factors;
|
||
}
|
||
|
||
function square() {
|
||
do_modified(this.reduce());
|
||
|
||
this[KEY_NUMERATOR].square();
|
||
|
||
if (KEY_DENOMINATOR in this)
|
||
this[KEY_DENOMINATOR].square();
|
||
|
||
return this;
|
||
}
|
||
|
||
// Exponentiation 冪/乘方: base ^ exponent = power
|
||
// https://en.wikipedia.org/wiki/Exponentiation
|
||
// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
|
||
function power(exponent) {
|
||
do_modified(this.reduce());
|
||
|
||
this[KEY_NUMERATOR].power(exponent);
|
||
|
||
if (KEY_DENOMINATOR in this)
|
||
this[KEY_DENOMINATOR].power(exponent);
|
||
|
||
return this;
|
||
}
|
||
|
||
// WARNING 注意: this will get floor(sqrt(this)),結果僅會回傳整數!
|
||
function square_root(precision) {
|
||
do_modified(this.reduce());
|
||
|
||
if (KEY_DENOMINATOR in this) {
|
||
this[KEY_NUMERATOR].divide(this[KEY_DENOMINATOR], precision);
|
||
delete this[KEY_DENOMINATOR];
|
||
}
|
||
|
||
this[KEY_NUMERATOR].square_root(precision);
|
||
|
||
return this;
|
||
}
|
||
|
||
function log(base) {
|
||
var value = this[KEY_NUMERATOR].log(base);
|
||
|
||
if (KEY_DENOMINATOR in this)
|
||
value -= this[KEY_DENOMINATOR].log(base);
|
||
|
||
return value;
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
|
||
// WARNING 注意: 若回傳非 Number.isSafeInteger(),則會有誤差,不能等於最佳近似值。
|
||
function valueOf(type) {
|
||
var n = this[KEY_NUMERATOR].valueOf(), d;
|
||
if (KEY_DENOMINATOR in this)
|
||
if (isFinite(d = this[KEY_DENOMINATOR].valueOf()) || isFinite(n))
|
||
n /= d;
|
||
else
|
||
n = this[KEY_NUMERATOR].ratio_to(this[KEY_DENOMINATOR]);
|
||
return n;
|
||
}
|
||
|
||
// default: numerator/denominator
|
||
// e.g., 1 2/3
|
||
var TYPE_MIX = 1,
|
||
// e.g., 1.2¯3
|
||
TYPE_DECIMAL = 2,
|
||
// Parentheses, e.g., 1.2(3)
|
||
TYPE_PARENTHESES = 3;
|
||
function toString(type) {
|
||
if (!(KEY_DENOMINATOR in this))
|
||
return this[KEY_NUMERATOR].toString();
|
||
|
||
var string = this[KEY_NUMERATOR];
|
||
if (type === TYPE_DECIMAL || type === TYPE_PARENTHESES) {
|
||
string = string.precise_divide(this[KEY_DENOMINATOR]);
|
||
if (string[2]) {
|
||
string[1] += type === TYPE_DECIMAL
|
||
// e.g., 1.2¯3
|
||
? '¯' + string[2]
|
||
// Parentheses, e.g., 1.2(3)
|
||
: '(' + string[2] + ')';
|
||
// using the combining overline (U+0305)
|
||
// https://en.wikipedia.org/wiki/Vinculum_(symbol)#Computer_entry_of_the_symbol
|
||
// https://en.wikipedia.org/wiki/Overline
|
||
//string[1] += string[2].replace(/(.)/g, '$1̅');
|
||
}
|
||
if (string[1])
|
||
string[0] += radix_point + string[1];
|
||
return string[0];
|
||
}
|
||
|
||
return (type === TYPE_MIX ? (string = string.clone()).division(this[KEY_DENOMINATOR]).toString() + ' ' + string.abs().toString() : string.toString())
|
||
//
|
||
+ '/' + this[KEY_DENOMINATOR].toString();
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------------------------//
|
||
|
||
return Rational;
|
||
}
|
||
|
||
});
|