UNPKG

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
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() ? '-' : '') + '&infin;'; } } /** * 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>&sdot;</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>&infin;</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