mathjslab
Version:
MathJSLab - An interpreter with language syntax like MATLAB®/Octave. ISBN 978-65-00-82338-7
1,388 lines (1,275 loc) • 58.8 kB
text/typescript
import { Decimal } from 'decimal.js';
/**
* decimal.js and ComplexDecimal configuration parameters.
*/
/**
* The maximum number of significant digits of the result of an operation.
* Values equal to or greater than 336 is used to produce correct rounding of
* trigonometric functions.
*/
const DecimalPrecision = 336;
/**
* Number of significant digits to reduce precision in compare operations and
* unparse.
*/
const DecimalPrecisionCompare = 7;
/**
* The default rounding mode used when rounding the result of an operation to
* precision significant digits.
*/
const DecimalRounding = Decimal.ROUND_HALF_DOWN;
/**
* The positive exponent value at and above which toString returns exponential
* notation.
*/
const DecimaltoExpPos = 20;
/**
* The negative exponent value at and below which toString returns exponential
* notation.
*/
const DecimaltoExpNeg = -7;
/**
* decimal.js setup.
*/
Decimal.set({
precision: DecimalPrecision,
rounding: DecimalRounding,
toExpNeg: DecimaltoExpNeg,
toExpPos: DecimaltoExpPos,
});
/**
* Binary operations name type.
*/
export type TBinaryOperationName =
| 'add'
| 'sub'
| 'mul'
| 'rdiv'
| 'ldiv'
| 'power'
| 'lt'
| 'le'
| 'eq'
| 'ge'
| 'gt'
| 'ne'
| 'and'
| 'or'
| 'xor'
| 'mod'
| 'rem'
| 'minWise'
| 'maxWise';
/**
* Unary operations name type.
*/
export type TUnaryOperationLeftName = 'copy' | 'neg' | 'not';
/**
* # ComplexDecimal
*
* An arbitrary precision complex number library.
*
* ## References
* * https://mathworld.wolfram.com/ComplexNumber.html
*/
export class ComplexDecimal {
/**
* Functions with one argument (mappings)
*/
public static mapFunction: Record<string, Function> = {
real: ComplexDecimal.real,
imag: ComplexDecimal.imag,
logical: ComplexDecimal.logical,
abs: ComplexDecimal.abs,
arg: ComplexDecimal.arg,
conj: ComplexDecimal.conj,
fix: ComplexDecimal.fix,
ceil: ComplexDecimal.ceil,
floor: ComplexDecimal.floor,
round: ComplexDecimal.round,
sign: ComplexDecimal.sign,
sqrt: ComplexDecimal.sqrt,
exp: ComplexDecimal.exp,
log: ComplexDecimal.log,
log2: ComplexDecimal.log2,
log10: ComplexDecimal.log10,
deg2rad: ComplexDecimal.deg2rad,
rad2deg: ComplexDecimal.rad2deg,
sin: ComplexDecimal.sin,
sind: ComplexDecimal.sind,
cos: ComplexDecimal.cos,
cosd: ComplexDecimal.cosd,
tan: ComplexDecimal.tan,
tand: ComplexDecimal.tand,
csc: ComplexDecimal.csc,
cscd: ComplexDecimal.cscd,
sec: ComplexDecimal.sec,
secd: ComplexDecimal.secd,
cot: ComplexDecimal.cot,
cotd: ComplexDecimal.cotd,
asin: ComplexDecimal.asin,
asind: ComplexDecimal.asind,
acos: ComplexDecimal.acos,
acosd: ComplexDecimal.acosd,
atan: ComplexDecimal.atan,
atand: ComplexDecimal.atand,
acsc: ComplexDecimal.acsc,
acscd: ComplexDecimal.acscd,
asec: ComplexDecimal.asec,
asecd: ComplexDecimal.asecd,
acot: ComplexDecimal.acot,
acotd: ComplexDecimal.acotd,
sinh: ComplexDecimal.sinh,
cosh: ComplexDecimal.cosh,
tanh: ComplexDecimal.tanh,
csch: ComplexDecimal.csch,
sech: ComplexDecimal.sech,
coth: ComplexDecimal.coth,
asinh: ComplexDecimal.asinh,
acosh: ComplexDecimal.acosh,
atanh: ComplexDecimal.atanh,
acsch: ComplexDecimal.acsch,
asech: ComplexDecimal.asech,
acoth: ComplexDecimal.acoth,
gamma: ComplexDecimal.gamma,
factorial: ComplexDecimal.factorial,
};
/**
* Functions with two arguments.
*/
public static twoArgFunction: Record<string, Function> = {
root: ComplexDecimal.root,
hypot: ComplexDecimal.hypot,
power: ComplexDecimal.power,
logb: ComplexDecimal.logb,
};
/**
* Most restricted number class.
*/
public static readonly numberClass: Record<string, number> = {
logical: 0,
real: 1,
complex: 2,
};
/**
* Real, imaginary and type properties.
*/
public re: Decimal;
public im: Decimal;
public type: number;
public parent: any;
public static setNumberType(value: ComplexDecimal): void {
if (value.im.eq(0)) {
if (!((value.re.eq(0) || value.re.eq(1)) && value.type === ComplexDecimal.numberClass.logical)) {
value.type = ComplexDecimal.numberClass.real;
}
} else {
value.type = ComplexDecimal.numberClass.complex;
}
}
/**
* ComplexDecimal constructor
* @param re Real part (optional).
* @param im Imaginary part (optional).
* @param type Class 'complex' | 'logical' (optional).
*/
public constructor(re?: number | string | Decimal, im?: number | string | Decimal, type?: number) {
this.re = re ? new Decimal(re) : new Decimal(0);
this.im = im ? new Decimal(im) : new Decimal(0);
this.type = type ?? ComplexDecimal.numberClass.complex;
if (this.im.eq(0)) {
if (!((this.re.eq(0) || this.re.eq(1)) && this.type === ComplexDecimal.numberClass.logical)) {
this.type = ComplexDecimal.numberClass.real;
}
} else {
this.type = ComplexDecimal.numberClass.complex;
}
}
/**
* Real part of complex number.
* @param z value.
* @returns Real part of z
*/
public static real(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(z.re);
}
/**
* Imaginary part of complex number.
* @param z value.
* @returns Imaginary part of z
*/
public static imag(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(z.im);
}
/**
* Check if object is a ComplexDecimal compatible.
* @param obj Any object to check if is CompleDecimal compatible.
* @returns boolean result.
*/
public static isThis(obj: any): boolean {
return 're' in obj;
}
/**
* Create new ComplexDecimal.
* @param re Real part (optional).
* @param im Imaginary part (optional).
* @param type Class 'complex' | 'logical' (optional).
* @returns new ComplexDecimal(re, im, type).
*/
public static newThis(re?: number | string | Decimal, im?: number | string | Decimal, type?: number): ComplexDecimal {
return new ComplexDecimal(re, im, type);
}
/**
* Parse string returning its ComplexDecimal value.
* @param value String to parse.
* @returns ComplexDecimal parsed value.
*/
public static parse(value: string): ComplexDecimal {
const num = (value as string).toLowerCase().replace('d', 'e');
if (num[num.length - 1] == 'i' || num[num.length - 1] == 'j') {
return new ComplexDecimal(0, num.substring(0, num.length - 1));
} else {
return new ComplexDecimal(num, 0);
}
}
/**
* Unparse real or imaginary part.
* @param value Decimal value
* @returns String of unparsed value
*/
private static unparseDecimal(value: Decimal): string {
if (value.isFinite()) {
const value_unparsed = value.toString().split('e');
if (value_unparsed.length == 1) {
return value_unparsed[0].slice(0, Decimal.toExpPos);
} else {
return value_unparsed[0].slice(0, Decimal.toExpPos) + 'e' + Number(value_unparsed[1]);
}
} else {
return value.isNaN() ? 'NaN' : (value.isNegative() ? '-' : '') + '∞';
}
}
/**
* Unparse ComplexDecimal value. Show true/false if logical value,
* otherwise show real and imaginary parts enclosed by parenthesis. If
* some part is zero the null part is ommited (and parenthesis is ommited
* too).
* @param value Value to unparse.
* @returns String of unparsed value.
*/
public static unparse(value: ComplexDecimal): string {
if (value.type !== ComplexDecimal.numberClass.logical) {
const value_prec = ComplexDecimal.toMaxPrecision(value);
if (!value_prec.re.eq(0) && !value_prec.im.eq(0)) {
return (
'(' +
ComplexDecimal.unparseDecimal(value_prec.re) +
(value_prec.im.gt(0) ? '+' : '') +
(!value_prec.im.eq(1) ? (!value_prec.im.eq(-1) ? ComplexDecimal.unparseDecimal(value_prec.im) : '-') : '') +
'i)'
);
} else if (!value_prec.re.eq(0)) {
return ComplexDecimal.unparseDecimal(value_prec.re);
} else if (!value_prec.im.eq(0)) {
return (!value_prec.im.eq(1) ? (!value_prec.im.eq(-1) ? ComplexDecimal.unparseDecimal(value_prec.im) : '-') : '') + 'i';
} else {
return '0';
}
} else {
if (value.re.eq(0)) {
return 'false';
} else {
return 'true';
}
}
}
/**
* Unparse real or imaginary part.
* @param value Decimal value
* @returns string of value unparsed
*/
private static unparseDecimalML(value: Decimal): string {
if (value.isFinite()) {
const value_unparsed = value.toString().split('e');
if (value_unparsed.length == 1) {
return '<mn>' + value_unparsed[0].slice(0, Decimal.toExpPos) + '</mn>';
} else {
return (
'<mn>' +
value_unparsed[0].slice(0, Decimal.toExpPos) +
'</mn><mo>⋅</mo><msup><mrow><mn>10</mn></mrow><mrow><mn>' +
Number(value_unparsed[1]) +
'</mn></mrow></msup>'
);
}
} else {
return value.isNaN() ? '<mi><b>NaN</b></mi>' : (value.isNegative() ? '<mo>-</mo>' : '') + '<mi>∞</mi>';
}
}
/**
* Unparse ComplexDecimal value as MathML language. Show true/false if
* logical value, otherwise show real and imaginary parts enclosed by
* parenthesis. If some part is zero the null part is ommited (and
* parenthesis is ommited too).
* @param value value to unparse.
* @returns string of unparsed value.
*/
public static unparseMathML(value: ComplexDecimal): string {
if (value.type !== ComplexDecimal.numberClass.logical) {
const value_prec = ComplexDecimal.toMaxPrecision(value);
if (!value_prec.re.eq(0) && !value_prec.im.eq(0)) {
return (
'<mo>(</mo>' +
ComplexDecimal.unparseDecimalML(value_prec.re) +
(value_prec.im.gt(0) ? '<mo>+</mo>' : '') +
(!value_prec.im.eq(1) ? (!value_prec.im.eq(-1) ? ComplexDecimal.unparseDecimalML(value_prec.im) : '<mo>-</mo>') : '') +
'<mi>i</mi><mo>)</mo>'
);
} else if (!value_prec.re.eq(0)) {
return ComplexDecimal.unparseDecimalML(value_prec.re);
} else if (!value_prec.im.eq(0)) {
return (!value_prec.im.eq(1) ? (!value_prec.im.eq(-1) ? ComplexDecimal.unparseDecimalML(value_prec.im) : '<mo>-</mo>') : '') + '<mi>i</mi>';
} else {
return '<mn>0</mn>';
}
} else {
if (value.re.eq(0)) {
return '<mi>false</mi>';
} else {
return '<mi>true</mi>';
}
}
}
/**
* Creates a copy of the value.
* @param value ComplexDecimal value
* @returns copy of value
*/
public static copy(value: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(value.re, value.im, value.type);
}
/**
* Reduce precision of real or imaginary part.
* @param value Full precision value.
* @returns Reduced precision value.
*/
private static toMaxPrecisionDecimal(value: Decimal): Decimal {
return value.toSignificantDigits(Decimal.precision - DecimalPrecisionCompare).toDecimalPlaces(Decimal.precision - DecimalPrecisionCompare);
}
/**
* Reduce precision.
* @param value Full precision value.
* @returns Reduced precision value.
*/
public static toMaxPrecision(value: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(
value.re.toSignificantDigits(Decimal.precision - DecimalPrecisionCompare).toDecimalPlaces(Decimal.precision - DecimalPrecisionCompare),
value.im.toSignificantDigits(Decimal.precision - DecimalPrecisionCompare).toDecimalPlaces(Decimal.precision - DecimalPrecisionCompare),
);
}
/**
* Get the minimal diference of two consecutive numbers for real or
* imaginary part, the floating-point relative accuracy.
* @returns Minimal diference of two consecutive numbers.
*/
private static epsilonDecimal(): Decimal {
return Decimal.pow(10, -Decimal.precision + DecimalPrecisionCompare);
}
/**
* Get the minimal diference of two consecutive numbers, the
* floating-point relative accuracy.
* @returns Minimal diference of two consecutive numbers.
*/
public static epsilon(): ComplexDecimal {
return new ComplexDecimal(ComplexDecimal.epsilonDecimal());
}
/**
* Test for equality.
* @param left Value.
* @param right Value.
* @returns Returns ComplexDecimal.true() if left and right are equals.
*/
public static eq(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const left_prec = ComplexDecimal.toMaxPrecision(left);
const right_prec = ComplexDecimal.toMaxPrecision(right);
return left_prec.re.eq(right_prec.re) && left_prec.im.eq(right_prec.im) ? ComplexDecimal.true() : ComplexDecimal.false();
}
/**
* Test for inequality.
* @param left Value.
* @param right Value.
* @returns Returns ComplexDecimal.true() if left and right are different.
*/
public static ne(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const left_prec = ComplexDecimal.toMaxPrecision(left);
const right_prec = ComplexDecimal.toMaxPrecision(right);
return left_prec.re.eq(right_prec.re) && left_prec.im.eq(right_prec.im) ? ComplexDecimal.false() : ComplexDecimal.true();
}
/**
* Comparison made in polar lexicographical ordering. It's ordered by
* absolute value, or by polar angle in (-pi,pi] when absolute values are
* equal.
* @param cmp Type of comparison.
* @param left Value.
* @param right Value.
* @returns Result of comparison as ComplexDecimal.true() or ComplexDecimal.false().
*/
private static cmp(cmp: 'lt' | 'lte' | 'gt' | 'gte', left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const left_prec = ComplexDecimal.toMaxPrecision(left);
const right_prec = ComplexDecimal.toMaxPrecision(right);
if (left_prec.im.eq(0) && right_prec.im.eq(0)) {
return left_prec.re[cmp](right_prec.re) ? ComplexDecimal.true() : ComplexDecimal.false();
}
const left_abs = ComplexDecimal.toMaxPrecisionDecimal(ComplexDecimal.abs(left).re);
const right_abs = ComplexDecimal.toMaxPrecisionDecimal(ComplexDecimal.abs(right).re);
if (left_abs.eq(right_abs)) {
return ComplexDecimal.toMaxPrecisionDecimal(ComplexDecimal.arg(left).re)[cmp](ComplexDecimal.toMaxPrecisionDecimal(ComplexDecimal.arg(right).re))
? ComplexDecimal.true()
: ComplexDecimal.false();
} else {
return left_abs[cmp](right_abs) ? ComplexDecimal.true() : ComplexDecimal.false();
}
}
/**
* Gets the maximum or minimum of an array of ComplexDecimal using real comparison.
* @param cmp 'lt' for minimum or 'gt' for maximum.
* @param args ComplexDecimal values.
* @returns Minimum or maximum of ComplexDecimal values.
*/
public static minMaxArrayReal(cmp: 'lt' | 'gt', ...args: ComplexDecimal[]): ComplexDecimal {
return args.reduce((previous: ComplexDecimal, current: ComplexDecimal): ComplexDecimal => (previous.re[cmp](current.re) ? previous : current), args[0]);
}
public static minMaxArrayRealWithIndex(cmp: 'lt' | 'gt', ...args: ComplexDecimal[]): [ComplexDecimal, number] {
let index: number = 0;
const result = args.reverse().reduce(
(previous: ComplexDecimal, current: ComplexDecimal, i: number): ComplexDecimal => {
if (previous.re[cmp](current.re)) {
return previous;
} else {
index = args.length - 1 - i;
return current;
}
},
args[args.length - 1],
);
return [result, index];
}
/**
* Gets the maximum or minimum of an array of ComplexDecimal using complex
* comparison. The arguments are in polar lexicographical ordering
* (ordered by absolute value, or by polar angle in (-pi,pi] when absolute
* values are equal).
* @param cmp 'lt' for minimum or 'gt' for maximum.
* @param args ComplexDecimal values.
* @returns Minimum or maximum of ComplexDecimal values.
*/
public static minMaxArrayComplex(cmp: 'lt' | 'gt', ...args: ComplexDecimal[]): ComplexDecimal {
return args.reduce((previous: ComplexDecimal, current: ComplexDecimal): ComplexDecimal => {
const previous_abs = ComplexDecimal.abs(previous).re;
const current_abs = ComplexDecimal.abs(current).re;
if (previous_abs.eq(current_abs)) {
return ComplexDecimal.arg(previous).re[cmp](ComplexDecimal.arg(current).re) ? previous : current;
} else {
return previous_abs[cmp](current_abs) ? previous : current;
}
}, args[0]);
}
public static minMaxArrayComplexWithIndex(cmp: 'lt' | 'gt', ...args: ComplexDecimal[]): [ComplexDecimal, number] {
let index: number = 0;
const result = args.reverse().reduce(
(previous: ComplexDecimal, current: ComplexDecimal, i: number): ComplexDecimal => {
const previous_abs = ComplexDecimal.abs(previous).re;
const current_abs = ComplexDecimal.abs(current).re;
if (previous_abs.eq(current_abs)) {
if (ComplexDecimal.arg(previous).re[cmp](ComplexDecimal.arg(current).re)) {
return previous;
} else {
index = args.length - 1 - i;
return current;
}
} else {
if (previous_abs[cmp](current_abs)) {
return previous;
} else {
index = args.length - 1 - i;
return current;
}
}
},
args[args.length - 1],
);
return [result, index];
}
/**
* Returns the minimum of arguments. The arguments are in polar
* lexicographical ordering (ordered by absolute value, or by polar angle
* in (-pi,pi] when absolute values are equal).
* @param left Value to compare.
* @param right Value to compare.
* @returns Minimum of left and right
*/
public static min(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
if (left.im.eq(0) && right.im.eq(0)) {
return left.re.lt(right.re) ? left : right;
} else {
const left_abs = ComplexDecimal.abs(left).re;
const right_abs = ComplexDecimal.abs(right).re;
if (left_abs.eq(right_abs)) {
return ComplexDecimal.arg(left).re.lt(ComplexDecimal.arg(right).re) ? left : right;
} else {
return left_abs.lt(right_abs) ? left : right;
}
}
}
public static minWise(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
if (left.type <= ComplexDecimal.numberClass.real && left.type <= ComplexDecimal.numberClass.real) {
return left.re.lt(right.re) ? left : right;
} else {
const left_abs = ComplexDecimal.abs(left).re;
const right_abs = ComplexDecimal.abs(right).re;
if (left_abs.eq(right_abs)) {
return ComplexDecimal.arg(left).re.lt(ComplexDecimal.arg(right).re) ? left : right;
} else {
return left_abs.lt(right_abs) ? left : right;
}
}
}
/**
* Returns the maximum of arguments. The arguments are in polar
* lexicographical ordering (ordered by absolute value, or by polar angle
* in (-pi,pi] when absolute values are equal).
* @param left Value to compare.
* @param right Value to compare.
* @returns Maximum of left and right
*/
public static max(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
if (left.im.eq(0) && right.im.eq(0)) {
return left.re.gte(right.re) ? left : right;
} else {
const left_abs = ComplexDecimal.abs(left).re;
const right_abs = ComplexDecimal.abs(right).re;
if (left_abs.eq(right_abs)) {
return ComplexDecimal.arg(left).re.gte(ComplexDecimal.arg(right).re) ? left : right;
} else {
return left_abs.gte(right_abs) ? left : right;
}
}
}
public static maxWise(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
if (left.type <= ComplexDecimal.numberClass.real && left.type <= ComplexDecimal.numberClass.real) {
return left.re.gte(right.re) ? left : right;
} else {
const left_abs = ComplexDecimal.abs(left).re;
const right_abs = ComplexDecimal.abs(right).re;
if (left_abs.eq(right_abs)) {
return ComplexDecimal.arg(left).re.gte(ComplexDecimal.arg(right).re) ? left : right;
} else {
return left_abs.gte(right_abs) ? left : right;
}
}
}
/**
* Less than comparison (lexicographical ordering).
* @param left Value.
* @param right Value.
* @returns Result of comparison left<right as ComplexDecimal.true() or ComplexDecimal.false().
*/
public static lt(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.cmp('lt', left, right);
}
/**
* Less than or equal comparison (lexicographical ordering).
* @param left Value.
* @param right Value.
* @returns Result of comparison left<=right as ComplexDecimal.true() or ComplexDecimal.false().
*/
public static le(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.cmp('lte', left, right);
}
/**
* Greater than comparison (lexicographical ordering).
* @param left Value.
* @param right Value.
* @returns Result of comparison left>right as ComplexDecimal.true() or ComplexDecimal.false().
*/
public static gt(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.cmp('gt', left, right);
}
/**
* Greater than or equal comparison (lexicographical ordering).
* @param left Value.
* @param right Value.
* @returns Result of comparison left>=right as ComplexDecimal.true() or ComplexDecimal.false().
*/
public static ge(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.cmp('gte', left, right);
}
/**
* ComplexDecimal logical false.
* @returns new ComplexDecimal(0, 0, 'logical')
*/
public static false(): ComplexDecimal {
return new ComplexDecimal(0, 0, ComplexDecimal.numberClass.logical);
}
/**
* ComplexDecimal logical true.
* @returns new ComplexDecimal(1, 0, 'logical')
*/
public static true(): ComplexDecimal {
return new ComplexDecimal(1, 0, ComplexDecimal.numberClass.logical);
}
/**
* Convert numeric values to logicals.
* @param value ComplexDecimal decimal value.
* @returns ComplexDecimal logical value.
*/
public static logical(value: ComplexDecimal): ComplexDecimal {
const value_prec = ComplexDecimal.toMaxPrecision(value);
return new ComplexDecimal(value_prec.re.eq(0) && value_prec.im.eq(0) ? 0 : 1, 0, ComplexDecimal.numberClass.logical);
}
/**
* Logical **AND**.
* @param left Value.
* @param right Value.
* @returns left AND right.
*/
public static and(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const left_prec = ComplexDecimal.toMaxPrecision(left);
const right_prec = ComplexDecimal.toMaxPrecision(right);
return (left_prec.re.eq(0) && left_prec.im.eq(0)) || (right_prec.re.eq(0) && right_prec.im.eq(0)) ? ComplexDecimal.false() : ComplexDecimal.true();
}
/**
* Logical **OR**.
* @param left Value.
* @param right Value.
* @returns left OR right.
*/
public static or(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const left_prec = ComplexDecimal.toMaxPrecision(left);
const right_prec = ComplexDecimal.toMaxPrecision(right);
return left_prec.re.eq(0) && left_prec.im.eq(0) && right_prec.re.eq(0) && right_prec.im.eq(0) ? ComplexDecimal.false() : ComplexDecimal.true();
}
/**
* Logical **XOR**.
* @param left Value.
* @param right Value.
* @returns left XOR right.
*/
public static xor(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const left_prec = ComplexDecimal.toMaxPrecision(left);
const right_prec = ComplexDecimal.toMaxPrecision(right);
return (left_prec.re.eq(0) && left_prec.im.eq(0) && !(right_prec.re.eq(0) && right_prec.im.eq(0))) ||
(!(left_prec.re.eq(0) && left_prec.im.eq(0)) && right_prec.re.eq(0) && right_prec.im.eq(0))
? ComplexDecimal.true()
: ComplexDecimal.false();
}
/**
* Logical **NOT**.
* @param right Value.
* @returns NOT right.
*/
public static not(right: ComplexDecimal): ComplexDecimal {
const right_prec = ComplexDecimal.toMaxPrecision(right);
return right_prec.re.eq(0) && right_prec.im.eq(0) ? ComplexDecimal.true() : ComplexDecimal.false();
}
/**
* 0
* @returns 0 as ComplexDecimal.
*/
public static zero(): ComplexDecimal {
return new ComplexDecimal(0, 0);
}
/**
* 1
* @returns 1 as ComplexDecimal.
*/
public static one(): ComplexDecimal {
return new ComplexDecimal(1, 0);
}
/**
* 1/2
* @returns 1/2 as ComplexDecimal.
*/
public static onediv2(): ComplexDecimal {
return new ComplexDecimal(1 / 2, 0);
}
/**
* -1/2
* @returns -1/2 as ComplexDecimal.
*/
public static minusonediv2(): ComplexDecimal {
return new ComplexDecimal(-1 / 2, 0);
}
/**
* -1
* @returns -1 as ComplexDecimal.
*/
public static minusone(): ComplexDecimal {
return new ComplexDecimal(-1, 0);
}
/**
* pi
* @returns pi as ComplexDecimal.
*/
public static pi(): ComplexDecimal {
return new ComplexDecimal(Decimal.acos(-1), 0);
}
/**
* pi/2
* @returns pi/2 as ComplexDecimal.
*/
public static pidiv2(): ComplexDecimal {
return new ComplexDecimal(Decimal.div(Decimal.acos(-1), 2), 0);
}
/**
* i
* @returns i as ComplexDecimal.
*/
public static onei(): ComplexDecimal {
return new ComplexDecimal(0, 1);
}
/**
* i/2
* @returns i/2 as ComplexDecimal.
*/
public static onediv2i(): ComplexDecimal {
return new ComplexDecimal(0, 1 / 2);
}
/**
* -i/2
* @returns -i/2 as ComplexDecimal.
*/
public static minusonediv2i(): ComplexDecimal {
return new ComplexDecimal(0, -1 / 2);
}
/**
* -i
* @returns -i as ComplexDecimal.
*/
public static minusonei(): ComplexDecimal {
return new ComplexDecimal(0, -1);
}
/**
* 2
* @returns 2 as ComplexDecimal.
*/
public static two(): ComplexDecimal {
return new ComplexDecimal(2, 0);
}
/**
* sqrt(2*pi)
* @returns sqrt(2*pi) as ComplexDecimal.
*/
public static sqrt2pi(): ComplexDecimal {
return new ComplexDecimal(Decimal.sqrt(Decimal.mul(2, Decimal.acos(-1))), 0);
}
/**
* e (Napier number).
* @returns e as ComplexDecimal.
*/
public static e(): ComplexDecimal {
return new ComplexDecimal(Decimal.exp(1), 0);
}
/**
* NaN
* @returns NaN as ComplexDecimal.
*/
public static NaN_0(): ComplexDecimal {
return new ComplexDecimal(NaN, 0);
}
/**
* Inf
* @returns Inf as ComplexDecimal.
*/
public static inf_0(): ComplexDecimal {
return new ComplexDecimal(Infinity, 0);
}
/**
* Addition of ComplexDecimal numbers.
* @param left Value.
* @param right Value.
* @returns left + right
*/
public static add(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.add(left.re, right.re), Decimal.add(left.im, right.im));
}
/**
* Subtraction of ComplexDecimal numbers.
* @param left Value
* @param right Value
* @returns left - right
*/
public static sub(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.sub(left.re, right.re), Decimal.sub(left.im, right.im));
}
/**
* Negates the ComplexDecimal number.
* @param z Value.
* @returns -z
*/
public static neg(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(z.re.neg(), z.im.neg());
}
/**
* Multiplication of ComplexDecimal numbers.
* @param left Value.
* @param right Value.
* @returns left * right
*/
public static mul(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
if (left.im.eq(0) && right.im.eq(0)) {
return new ComplexDecimal(Decimal.mul(left.re, right.re), new Decimal(0));
} else {
return new ComplexDecimal(
Decimal.sub(Decimal.mul(left.re, right.re), Decimal.mul(left.im, right.im)),
Decimal.add(Decimal.mul(left.re, right.im), Decimal.mul(left.im, right.re)),
);
}
}
/**
* Right Division.
* @param left Dividend.
* @param right Divisor.
* @returns left / right.
*/
public static rdiv(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
const denom = Decimal.add(Decimal.mul(right.re, right.re), Decimal.mul(right.im, right.im));
if (denom.isFinite()) {
if (denom.eq(0)) {
return new ComplexDecimal(Decimal.mul(left.re, Infinity), left.im.eq(0) ? new Decimal(0) : Decimal.mul(left.im, Infinity));
} else {
return new ComplexDecimal(
Decimal.div(Decimal.add(Decimal.mul(left.re, right.re), Decimal.mul(left.im, right.im)), denom),
Decimal.div(Decimal.sub(Decimal.mul(left.im, right.re), Decimal.mul(left.re, right.im)), denom),
);
}
} else {
if (denom.isNaN()) {
if ((right.re.isFinite() || right.re.isNaN()) && (right.im.isFinite() || right.im.isNaN())) {
return new ComplexDecimal(NaN, 0);
} else {
return ComplexDecimal.zero();
}
} else if (left.re.isFinite() && left.im.isFinite()) {
return ComplexDecimal.zero();
} else {
return new ComplexDecimal(NaN, 0);
}
}
}
/**
* Left division. For ComplexDecimal the left division is the same of right division.
* @param left Dividend.
* @param right Divisor.
* @returns left \ right.
*/
public static ldiv = ComplexDecimal.rdiv;
/**
* Inverse.
* @param x Denominator
* @returns 1/x
*/
public static inv(x: ComplexDecimal): ComplexDecimal {
const denom = Decimal.add(Decimal.mul(x.re, x.re), Decimal.mul(x.im, x.im));
if (denom.isFinite()) {
if (denom.eq(0)) {
return new ComplexDecimal(Infinity, 0);
} else {
return new ComplexDecimal(Decimal.div(x.re, denom), Decimal.div(x.im, denom).neg());
}
} else {
if (denom.isNaN()) {
if ((x.re.isFinite() || x.re.isNaN()) && (x.im.isFinite() || x.im.isNaN())) {
return new ComplexDecimal(NaN, 0);
} else {
return ComplexDecimal.zero();
}
} else {
return ComplexDecimal.zero();
}
}
}
/**
* Power of ComplexDecimal numbers.
* @param left Base.
* @param right Exponent.
* @returns left^right
*/
public static power(left: ComplexDecimal, right: ComplexDecimal): ComplexDecimal {
if (left.im.eq(0) && right.im.eq(0) && left.re.gte(0)) {
return new ComplexDecimal(Decimal.pow(left.re, right.re), new Decimal(0));
} else {
const arg_left = Decimal.atan2(left.im.eq(0) ? 0 : left.im, left.re.eq(0) ? 0 : left.re);
const mod2_left = Decimal.add(Decimal.mul(left.re, left.re), Decimal.mul(left.im, left.im));
const mul = Decimal.mul(Decimal.pow(mod2_left, Decimal.div(right.re, 2)), Decimal.exp(Decimal.mul(Decimal.mul(-1, right.im), arg_left)));
const trig = Decimal.add(Decimal.mul(right.re, arg_left), Decimal.mul(Decimal.div(right.im, 2), Decimal.ln(mod2_left)));
return new ComplexDecimal(
Decimal.mul(mul, Decimal.cos(trig)),
left.im.eq(0) && right.im.eq(0) && (right.re.gte(1) || right.re.lte(-1)) ? 0 : Decimal.mul(mul, Decimal.sin(trig)),
);
}
}
/**
* Root of ComplexDecimal numbers.
* @param x Radicand.
* @param n Index.
* @returns nth root of x.
*/
public static root(x: ComplexDecimal, n: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.power(x, ComplexDecimal.inv(n));
}
/**
* Absolute value and complex magnitude.
* @param z value
* @returns Absolute value of z
*/
public static abs(z: ComplexDecimal): ComplexDecimal {
return z.im.eq(0) ? new ComplexDecimal(Decimal.abs(z.re)) : new ComplexDecimal(Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im))));
}
/**
* Square root of sum of squares (hypotenuse)
* @param x vertex.
* @param y vertex.
* @returns hypotenuse of the two orthogonal vertex x and y.
*/
public static hypot(x: ComplexDecimal, y: ComplexDecimal): ComplexDecimal {
const abs_x = Decimal.sqrt(Decimal.add(Decimal.mul(x.re, x.re), Decimal.mul(x.im, x.im)));
const abs_y = Decimal.sqrt(Decimal.add(Decimal.mul(y.re, y.re), Decimal.mul(y.im, y.im)));
return new ComplexDecimal(Decimal.sqrt(Decimal.add(Decimal.mul(abs_x, abs_x), Decimal.mul(abs_y, abs_y))));
}
/**
* Phase angle.
* @param z value.
* @returns Phase angle of z.
*/
public static arg(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.atan2(z.im.eq(0) ? 0 : z.im, z.re) /*test if imaginary part is 0 to change -0 to 0*/, 0);
}
/**
* Complex conjugate
* @param z value.
* @returns Complex conjugate of z
*/
public static conj(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(new Decimal(z.re), z.im.neg());
}
/**
* Remainder after division (modulo operation). By convention
* mod(a,0) = a.
* @param x Dividend.
* @param y Divisor.
* @returns Remainder after division.
*/
public static mod(x: ComplexDecimal, y: ComplexDecimal): ComplexDecimal {
if (!(x.im.eq(0) && y.im.eq(0))) {
throw new Error('mod: not defined for complex numbers');
}
if (y.re.eq(0)) {
return x;
} else {
return new ComplexDecimal(Decimal.mod(x.re, y.re));
}
}
/**
* Remainder after division. By convention rem(a,0) = NaN.
* @param x Dividend.
* @param y Divisor.
* @returns Remainder after division.
*/
public static rem(x: ComplexDecimal, y: ComplexDecimal): ComplexDecimal {
if (!(x.im.eq(0) && y.im.eq(0))) {
throw new Error('rem: not defined for complex numbers');
}
return new ComplexDecimal(Decimal.mod(x.re, y.re));
}
/**
* Round toward zero. This operation effectively truncates the number to
* integer by removing the decimal portion.
* @param z Value.
* @returns Integer portion of z.
*/
public static fix(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.trunc(z.re), Decimal.trunc(z.im));
}
/**
* Round toward positive infinity.
* @param z Value
* @returns Smallest integer greater than or equal to z.
*/
public static ceil(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.ceil(z.re), Decimal.ceil(z.im));
}
/**
* Round toward negative infinity.
* @param z Value
* @returns Largest integer less than or equal to z.
*/
public static floor(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.floor(z.re), Decimal.floor(z.im));
}
/**
* Round to nearest integer.
* @param z Value.
* @returns Nearest integer of z.
*/
public static round(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.round(z.re), Decimal.round(z.im));
}
/**
* Sign function (signum function).
* @param z Value.
* @returns
* * 1 if the corresponding element of z is greater than 0.
* * 0 if the corresponding element of z equals 0.
* * -1 if the corresponding element of z is less than 0.
* * z/abs(z) if z is complex.
*/
public static sign(z: ComplexDecimal): ComplexDecimal {
if (z.re.eq(0)) {
if (z.im.eq(0)) {
return ComplexDecimal.zero();
} else {
return new ComplexDecimal(0, Decimal.div(z.im, Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im)))));
}
} else {
if (z.im.eq(0)) {
return new ComplexDecimal(Decimal.div(z.re, Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im)))), 0);
} else {
return new ComplexDecimal(
Decimal.div(z.re, Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im)))),
Decimal.div(z.im, Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im)))),
);
}
}
}
/**
* Square root.
* @param z Value.
* @returns Square root of z.
*/
public static sqrt(z: ComplexDecimal): ComplexDecimal {
const mod_z = Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im)));
const arg_z = Decimal.atan2(z.im.eq(0) ? 0 : z.im, z.re);
return new ComplexDecimal(Decimal.mul(Decimal.sqrt(mod_z), Decimal.cos(Decimal.div(arg_z, 2))), Decimal.mul(Decimal.sqrt(mod_z), Decimal.sin(Decimal.div(arg_z, 2))));
}
/**
* Exponential
* @param z Value.
* @returns Exponential of z.
*/
public static exp(z: ComplexDecimal): ComplexDecimal {
// E^x (exponential)
return new ComplexDecimal(Decimal.mul(Decimal.exp(z.re), Decimal.cos(z.im)), Decimal.mul(Decimal.exp(z.re), Decimal.sin(z.im)));
}
/**
* Natural logarithm.
* @param z Value.
* @returns Natural logarithm of z.
*/
public static log(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.ln(Decimal.sqrt(Decimal.add(Decimal.mul(z.re, z.re), Decimal.mul(z.im, z.im)))), Decimal.atan2(z.im.eq(0) ? 0 : z.im, z.re));
}
/**
* Compute the log using a specified base.
* @param b Base.
* @param l Value.
* @returns Logarith base b of l.
*/
public static logb(b: ComplexDecimal, l: ComplexDecimal): ComplexDecimal {
const mod_b = Decimal.sqrt(Decimal.add(Decimal.mul(b.re, b.re), Decimal.mul(b.im, b.im)));
if (mod_b.eq(0)) {
return ComplexDecimal.zero();
} else {
const arg_b = Decimal.atan2(b.im.eq(0) ? 0 : b.im, b.re);
const mod_l = Decimal.sqrt(Decimal.add(Decimal.mul(l.re, l.re), Decimal.mul(l.im, l.im)));
const arg_l = Decimal.atan2(l.im.eq(0) ? 0 : l.im, l.re);
const denom = Decimal.add(Decimal.mul(Decimal.ln(mod_b), Decimal.ln(mod_b)), Decimal.mul(arg_b, arg_b));
return new ComplexDecimal(
Decimal.div(Decimal.add(Decimal.mul(Decimal.ln(mod_l), Decimal.ln(mod_b)), Decimal.mul(arg_l, arg_b)), denom),
Decimal.div(Decimal.sub(Decimal.mul(arg_l, Decimal.ln(mod_b)), Decimal.mul(Decimal.ln(mod_l), arg_b)), denom),
);
}
}
/**
* Base 2 logarithm
* @param z Value
* @returns logarithm base 2 of z.
*/
public static log2(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.logb(new ComplexDecimal(2), z);
}
/**
* Common logarithm (base 10)
* @param z Value
* @returns logarithm base 10 of z.
*/
public static log10(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.logb(new ComplexDecimal(10), z);
}
/**
* Convert angle from degrees to radians.
* @param z Angle in degrees.
* @returns Angle in radians.
*/
public static deg2rad(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.mul(Decimal.div(Decimal.acos(-1), 180), z.re), Decimal.mul(Decimal.div(Decimal.acos(-1), 180), z.im));
}
/**
* Convert angle from radians to degrees.
* @param z Angle in radians.
* @returns Angle in degrees.
*/
public static rad2deg(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.mul(Decimal.div(180, Decimal.acos(-1)), z.re), Decimal.mul(Decimal.div(180, Decimal.acos(-1)), z.im));
}
/**
* Trignometric sine.
* @param z Argument in radians.
* @returns Sine of z.
*/
public static sin(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.mul(Decimal.sin(z.re), Decimal.cosh(z.im)), Decimal.mul(Decimal.cos(z.re), Decimal.sinh(z.im)));
}
/**
* Trignometric sine in degrees.
* @param z Argument in degrees.
* @returns Sine of z
*/
public static sind(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.sin(ComplexDecimal.deg2rad(z));
}
/**
* Trignometric cosine.
* @param z Argument in radians.
* @returns Cosine of z.
*/
public static cos(z: ComplexDecimal): ComplexDecimal {
return new ComplexDecimal(Decimal.mul(Decimal.cos(z.re), Decimal.cosh(z.im)), Decimal.mul(Decimal.sin(z.re), Decimal.sinh(z.im)).neg());
}
/**
* Trignometric cosine in degrees.
* @param z Argument in degrees.
* @returns Cosine of z
*/
public static cosd(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.cos(ComplexDecimal.deg2rad(z));
}
/**
* Trigonometric tangent. Implemented as: tan(z) = sin(z)/cos(z)
* @param z Argument in radians.
* @returns Tangent of z.
*/
public static tan(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rdiv(ComplexDecimal.sin(z), ComplexDecimal.cos(z));
}
/**
* Trigonometric tangent in degrees.
* @param z Argument in degrees.
* @returns Tangent of z.
*/
public static tand(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.tan(ComplexDecimal.deg2rad(z));
}
/**
* Trigonometric cosecant. Implemented as csc(z)=1/sin(z)
* @param z Argument in radians.
* @returns Cosecant of z.
*/
public static csc(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rdiv(ComplexDecimal.one(), ComplexDecimal.sin(z));
}
/**
* Trigonometric cosecant in degrees.
* @param z Argument in degrees.
* @returns Cosecant of z.
*/
public static cscd(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.csc(ComplexDecimal.deg2rad(z));
}
/**
* Trigonometric secant. Implemented as: sec(z) = 1/cos(z)
* @param z Argument in radians.
* @returns Secant of z.
*/
public static sec(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rdiv(ComplexDecimal.one(), ComplexDecimal.cos(z));
}
/**
* Trigonometric secant in degrees.
* @param z Argument in degrees.
* @returns Secant of z.
*/
public static secd(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.sec(ComplexDecimal.deg2rad(z));
}
/**
* Trigonometric cotangent. Implemented as: cot(z) = cos(z)/sin(z)
* @param z Argument in radians.
* @returns Cotangent of z.
*/
public static cot(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rdiv(ComplexDecimal.cos(z), ComplexDecimal.sin(z));
}
/**
* Trigonometric cotangent in degrees.
* @param z Argument in degrees.
* @returns Cotangent of z.
*/
public static cotd(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.cot(ComplexDecimal.deg2rad(z));
}
/**
* Inverse (arc) sine. Implemented as: asin(z) = I*ln(sqrt(1-z^2)-I*z)
* @param z Argument (unitless).
* @returns Inverse sine of z in radians.
*/
public static asin(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rdiv(
ComplexDecimal.onei(),
ComplexDecimal.log(
ComplexDecimal.sub(
ComplexDecimal.sqrt(ComplexDecimal.sub(ComplexDecimal.one(), ComplexDecimal.power(z, ComplexDecimal.two()))),
ComplexDecimal.mul(ComplexDecimal.onei(), z),
),
),
);
}
/**
* Inverse (arc) sine in degrees.
* @param z Argument (unitless).
* @returns Inverse sine of z in degrees.
*/
public static asind(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rad2deg(ComplexDecimal.asin(z));
}
/**
* Inverse (arc) cosine. Implemented as: acos(z) = pi/2-asin(z)
* @param z Argument (unitless).
* @returns Inverse cosine of z in radians.
*/
public static acos(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.sub(ComplexDecimal.pidiv2(), ComplexDecimal.asin(z));
}
/**
* Inverse (arc) cosine in degrees.
* @param z Argument (unitless).
* @returns Inverse cosine of z in degrees.
*/
public static acosd(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.rad2deg(ComplexDecimal.acos(z));
}
/**
* Inverse (arc) tangent. Implemented as: atan(z) = -I/2*ln((I-z)/(I+z))
* @param z Argument (unitless).
* @returns Inverse tangent of z in radians.
*/
public static atan(z: ComplexDecimal): ComplexDecimal {
return ComplexDecimal.mul(
ComplexDecimal.minusonediv2i(),
ComplexDecimal.log(ComplexDecimal.rdiv(ComplexDecimal.sub(ComplexDecimal.onei(), z), ComplexDecimal.add(ComplexDecimal.onei(), z))),
);
}
/**
* Inverse (arc) tangent in degrees.
* @param z Argument (unitless).
* @returns Inverse tange