UNPKG

@cortex-js/math-json

Version:

A JSON schema to represent math formulas

1,598 lines (1,455 loc) 356 kB
function stringToCodepoints(string) { const result = []; for (let i = 0; i < string.length; i++) { let code = string.charCodeAt(i); // if (code === 0x0d && string.charCodeAt(i + 1) === 0x0a) { // code = 0x0a; // i++; // } // if (code === 0x0d || code === 0x0c) code = 0x0a; // if (code === 0x00) code = 0xfffd; // Decode a surrogate pair into an astral codepoint. if (code >= 0xd800 && code <= 0xdbff) { const nextCode = string.charCodeAt(i + 1); if (nextCode >= 0xdc00 && nextCode <= 0xdfff) { const lead = code - 0xd800; const trail = nextCode - 0xdc00; code = 2 ** 16 + lead * 2 ** 10 + trail; // N = ((H - 0xD800) * 0x400) + (L - 0xDC00) + 0x10000; i++; } } result.push(code); } return result; } const ZWJ = 0x200d; // Zero-width joiner // const ZWSP = 0x200b; // Zero-width space // Regional indicator: a pair of codepoints indicating some flags const REGIONAL_INDICATOR = [0x1f1e6, 0x1f1ff]; function isEmojiCombinator(code) { // Zero-width joiner if (code === ZWJ) return true; // VS-15: text presentation, VS-16: Emoji presentation if (code === 0xfe0e || code === 0xfe0f) return true; // EMOJI_MODIFIER_FITZPATRICK_TYPE 1-6 if (code >= 0x1f3fb && code <= 0x1f3fb + 5) return true; // Red hair..white hair if (code >= 0x1f9b0 && code <= 0x1f9b0 + 4) return true; // EMOJI_TAG if (code >= 0xe0020 && code <= 0xe0020 + 96) return true; return false; } function isRegionalIndicator(code) { return code >= REGIONAL_INDICATOR[0] && code <= REGIONAL_INDICATOR[1]; } /** * Return a string or an array of graphemes. * * This includes: * - emoji with skin and hair modifiers * - emoji combination (for example "female pilot") * - text emoji with an emoji presentation style modifier * - U+1F512 U+FE0E 🔒︎ * - U+1F512 U+FE0F 🔒️ * - flags represented as two regional indicator codepoints * - flags represented as a flag emoji + zwj + an emoji tag * - other combinations (for example, rainbow flag) */ function splitGraphemes(string) { // If it's all ASCII, short-circuit the grapheme splitting... if (/^[\u0020-\u00FF]*$/.test(string)) return string; const result = []; const codePoints = stringToCodepoints(string); let index = 0; while (index < codePoints.length) { const code = codePoints[index++]; const next = codePoints[index]; // Combine sequences if (next === ZWJ) { // Zero-width joiner sequences are: // ZWJ_SEQUENCE := (CHAR + ZWJ)+ const baseIndex = index - 1; index += 2; while (codePoints[index] === ZWJ) { index += 2; } result.push(String.fromCodePoint(...codePoints.slice(baseIndex, 2 * index - baseIndex + 1))); } else if (isEmojiCombinator(next)) { // Combine emoji sequences // See http://unicode.org/reports/tr51/#def_emoji_tag_sequence const baseIndex = index - 1; // The previous character is the 'base' while (isEmojiCombinator(codePoints[index])) { index += codePoints[index] === ZWJ ? 2 : 1; } result.push(String.fromCodePoint(...codePoints.slice(baseIndex, 2 * index - baseIndex - 1))); } else if (isRegionalIndicator(code)) { // Some (but not all) flags are represented by a sequence of two // "regional indicators" codepoints. index += 1; result.push(String.fromCodePoint(...codePoints.slice(index - 2, 2))); } else { result.push(String.fromCodePoint(code)); } } return result; } /** * ## Reference * TeX source code: * {@link http://tug.org/texlive/devsrc/Build/source/texk/web2c/tex.web | Tex.web} * */ /** * Given a LaTeX expression represented as a character string, * the Lexer class will scan and return Tokens for the lexical * units in the string. * * @param s A string of LaTeX */ class Tokenizer { constructor(s) { this.obeyspaces = false; this.s = splitGraphemes(s); this.pos = 0; } /** * @return True if we reached the end of the stream */ end() { return this.pos >= this.s.length; } /** * Return the next char and advance */ get() { return this.pos < this.s.length ? this.s[this.pos++] : ''; } /** * Return the next char, but do not advance */ peek() { return this.s[this.pos]; } /** * Return the next substring matching regEx and advance. */ match(regEx) { // this.s can either be a string, if it's made up only of ASCII chars // or an array of graphemes, if it's more complicated. let execResult; if (typeof this.s === 'string') { execResult = regEx.exec(this.s.slice(this.pos)); } else { execResult = regEx.exec(this.s.slice(this.pos).join('')); } if (execResult === null || execResult === void 0 ? void 0 : execResult[0]) { this.pos += execResult[0].length; return execResult[0]; } return null; } /** * Return the next token, or null. */ next() { // If we've reached the end, exit if (this.end()) return null; // Handle white space // In text mode, spaces are significant, // however they are coalesced unless \obeyspaces if (!this.obeyspaces && this.match(/^[ \f\n\r\t\v\xA0\u2028\u2029]+/)) { // Note that browsers are inconsistent in their definitions of the // `\s` metacharacter, so we use an explicit pattern instead. // - IE: `[ \f\n\r\t\v]` // - Chrome: `[ \f\n\r\t\v\u00A0]` // - Firefox: `[ \f\n\r\t\v\u00A0\u2028\u2029]` // - \f \u000C: form feed (FORM FEED) // - \n \u000A: linefeed (LINE FEED) // - \r \u000D: carriage return // - \t \u0009: tab (CHARACTER TABULATION) // - \v \u000B: vertical tab (LINE TABULATION) // - \u00A0: NON-BREAKING SPACE // - \u2028: LINE SEPARATOR // - \u2029: PARAGRAPH SEPARATOR return '<space>'; } else if (this.obeyspaces && this.match(/^[ \f\n\r\t\v\xA0\u2028\u2029]/)) { // Don't coalesce when this.obeyspaces is true (different regex from above) return '<space>'; } const next = this.get(); // Is it a command? if (next === '\\') { if (!this.end()) { // A command is either a string of letters and asterisks... let command = this.match(/^[a-zA-Z*]+/); if (command) { // Spaces after a 'control word' are ignored // (but not after a 'control symbol' (single char) this.match(/^[ \f\n\r\t\v\xA0\u2028\u2029]*/); } else { // ... or a single non-letter character command = this.get(); if (command === ' ') { // The `\ ` command is equivalent to a single space return '<space>'; } } return '\\' + command; } } else if (next === '{') { // This is a group start return '<{>'; } else if (next === '}') { // This is a group end return '<}>'; } else if (next === '^') { if (this.peek() === '^') { // It might be a ^^ command (inline hex character) this.get(); // There can be zero to six carets with the same number of hex digits const hex = this.match(/^(\^(\^(\^(\^[0-9a-f])?[0-9a-f])?[0-9a-f])?[0-9a-f])?[0-9a-f][0-9a-f]/); if (hex) { return String.fromCodePoint(parseInt(hex.slice(hex.lastIndexOf('^') + 1), 16)); } } return next; } else if (next === '#') { // This could be either a param token, or a literal # (used for // colorspecs, for example). A param token is a '#' followed by // - a digit 0-9 followed by a non-alpha, non-digit // - or '?'. // Otherwise, it's a literal '#'. if (!this.end()) { let isParam = false; if (/[0-9?]/.test(this.peek())) { // Could be a param isParam = true; // Need to look ahead to the following char if (this.pos + 1 < this.s.length) { const after = this.s[this.pos + 1]; isParam = /[^0-9A-Za-z]/.test(after); } } if (isParam) { return '#' + this.get(); } return '#'; } } else if (next === '$') { // Mode switch if (this.peek() === '$') { // $$ this.get(); return '<$$>'; } // $ return '<$>'; } return next; } } // Some primitive commands need to be handled in the expansion phase // (the 'gullet') function expand(lex, args) { var _a, _b, _c, _d; let result = []; let token = lex.next(); if (token) { if (token === '\\relax') ; else if (token === '\\noexpand') { // Do not expand the next token token = lex.next(); if (token) { result.push(token); } } else if (token === '\\obeyspaces') { lex.obeyspaces = true; } else if (token === '\\space' || token === '~') { // The `\space` command is equivalent to a single space // The ~ is an 'active character' (a single character macro) // that maps to <space> result.push('<space>'); } else if (token === '\\bgroup') { // Begin group, synonym for opening brace result.push('<{>'); } else if (token === '\\egroup') { // End group, synonym for closing brace result.push('<}>'); } else if (token === '\\string') { // Turn the next token into a string token = lex.next(); if (token) { if (token[0] === '\\') { Array.from(token).forEach((x) => result.push(x === '\\' ? '\\backslash' : x)); } else if (token === '<{>') { result.push('\\{'); } else if (token === '<space>') { result.push('~'); } else if (token === '<}>') { result.push('\\}'); } } } else if (token === '\\csname') { // Turn the next tokens, until `\endcsname`, into a command while (lex.peek() === '<space>') { lex.next(); } let command = ''; let done = false; let tokens = []; do { if (tokens.length === 0) { // We're out of tokens to look at, get some more if (/^#[0-9?]$/.test(lex.peek())) { // Expand parameters (but not commands) const param = lex.get().slice(1); tokens = tokenize((_b = (_a = args === null || args === void 0 ? void 0 : args[param]) !== null && _a !== void 0 ? _a : args === null || args === void 0 ? void 0 : args['?']) !== null && _b !== void 0 ? _b : '\\placeholder{}', args); token = tokens[0]; } else { token = lex.next(); tokens = token ? [token] : []; } } done = tokens.length === 0; if (!done && token === '\\endcsname') { done = true; tokens.shift(); } if (!done) { done = token === '<$>' || token === '<$$>' || token === '<{>' || token === '<}>' || (!!token && token.length > 1 && token[0] === '\\'); } if (!done) { command += tokens.shift(); } } while (!done); if (command) { result.push('\\' + command); } result = result.concat(tokens); } else if (token === '\\endcsname') ; else if (token.length > 1 && token[0] === '#') { // It's a parameter to expand const param = token.slice(1); result = result.concat(tokenize((_d = (_c = args === null || args === void 0 ? void 0 : args[param]) !== null && _c !== void 0 ? _c : args === null || args === void 0 ? void 0 : args['?']) !== null && _d !== void 0 ? _d : '\\placeholder{}', args)); } else { result.push(token); } } return result; } /** * Create Tokens from a stream of LaTeX * * @param s - A string of LaTeX. It can include comments (with the `%` * marker) and multiple lines. */ function tokenize(s, args) { // Merge multiple lines into one, and remove comments const lines = s.toString().split(/\r?\n/); let stream = ''; let sep = ''; for (const line of lines) { stream += sep; sep = ' '; // Remove everything after a % (comment marker) // (but \% should be preserved...) const m = line.match(/((?:\\%)|[^%])*/); if (m !== null) stream += m[0]; } const tokenizer = new Tokenizer(stream); let result = []; do { result = result.concat(expand(tokenizer, args)); } while (!tokenizer.end()); return result; } function joinLatex(segments) { let sep = ''; let result = ''; for (const segment of segments) { if (segment) { if (/[a-zA-Z*]/.test(segment[0])) { // If the segment begins with a char that *could* be in a command // name... insert a separator (if one was needed for the previous segment) result += sep; } // If the segment ends in a command... if (/\\[a-zA-Z]+\*?$/.test(segment)) { // ... potentially add a space before the next segment sep = ' '; } else { sep = ''; } result += segment; } } return result; } function tokensToString(tokens) { let flat = []; if (Array.isArray(tokens)) { for (const item of tokens) { if (Array.isArray(item)) { flat = [...flat, ...item]; } else { flat.push(item); } } } else { flat = [tokens]; } const result = joinLatex(flat.map((token) => { var _a; return ((_a = { '<space>': ' ', '<$$>': '$$', '<$>': '$', '<{>': '{', '<}>': '}', }[token]) !== null && _a !== void 0 ? _a : token); })); return result; } const DEFINITIONS_INEQUALITIES = [ { name: 'NotLess', trigger: { infix: ['!', '<'] }, associativity: 'right', precedence: 246, }, { name: 'NotLess', trigger: { infix: '\\nless' }, associativity: 'right', precedence: 246, }, { name: 'Less', trigger: { infix: '<' }, associativity: 'right', precedence: 245, }, { name: 'Less', trigger: { infix: '\\lt' }, associativity: 'right', precedence: 245, }, { name: 'LessEqual', trigger: { infix: ['<', '='] }, associativity: 'right', precedence: 241, }, { name: 'LessEqual', trigger: { infix: '\\le' }, associativity: 'right', precedence: 241, }, { name: 'LessEqual', trigger: { infix: '\\leq' }, associativity: 'right', precedence: 241, }, { name: 'LessEqual', trigger: { infix: '\\leqslant' }, associativity: 'right', precedence: 265, // Note different precendence than `<=` as per MathML }, { name: 'LessNotEqual', trigger: { infix: '\\lneqq' }, associativity: 'right', precedence: 260, }, { name: 'NotLessNotEqual', trigger: { infix: '\\nleqq' }, associativity: 'right', precedence: 260, }, { name: 'LessOverEqual', trigger: { infix: '\\leqq' }, associativity: 'right', precedence: 265, }, { name: 'GreaterOverEqual', trigger: { infix: '\\geqq' }, associativity: 'right', precedence: 265, }, { name: 'Equal', trigger: { infix: '=' }, associativity: 'right', precedence: 260, }, { name: 'StarEqual', trigger: { infix: ['*', '='] }, associativity: 'right', precedence: 260, }, { name: 'StarEqual', trigger: { infix: ['\\star', '='] }, associativity: 'right', precedence: 260, }, { name: 'PlusEqual', trigger: { infix: ['+', '='] }, associativity: 'right', precedence: 260, }, { name: 'MinusEqual', trigger: { infix: ['-', '='] }, associativity: 'right', precedence: 260, }, { name: 'SlashEqual', trigger: { infix: ['/', '='] }, associativity: 'right', precedence: 260, }, { name: 'EqualEqual', trigger: { infix: ['=', '='] }, associativity: 'right', precedence: 260, }, { name: 'EqualEqualEqual', trigger: { infix: ['=', '=', '='] }, associativity: 'right', precedence: 265, }, { name: 'TildeFullEqual', trigger: { infix: '\\cong' }, associativity: 'right', precedence: 260, }, { name: 'NotTildeFullEqual', trigger: { infix: '\\ncong' }, associativity: 'right', precedence: 260, }, { name: 'Assign', trigger: { infix: [':', '='] }, associativity: 'right', precedence: 260, }, { name: 'Assign', trigger: { infix: '\\coloneq' }, associativity: 'right', precedence: 260, }, { name: 'Approx', trigger: { infix: '\\approx' }, associativity: 'right', precedence: 247, }, { name: 'NotApprox', trigger: { infix: '\\approx' }, associativity: 'right', precedence: 247, }, { name: 'ApproxEqual', trigger: { infix: '\\approxeq' }, associativity: 'right', precedence: 260, }, { name: 'NotApproxEqual', trigger: { infix: ['!', '\\approxeq'] }, associativity: 'right', precedence: 250, }, { name: 'NotEqual', trigger: { infix: '\\ne' }, associativity: 'right', precedence: 255, }, { name: 'Unequal', trigger: { infix: ['!', '='] }, associativity: 'right', precedence: 260, // Note different precendence than \\ne per MathML }, { name: 'GreaterEqual', trigger: { infix: '\\ge' }, associativity: 'right', precedence: 242, // Note: different precendence than `>=` as per MathML }, { name: 'GreaterEqual', trigger: { infix: '\\geq' }, associativity: 'right', precedence: 242, // Note: different precendence than `>=` as per MathML }, { name: 'GreaterEqual', trigger: { infix: ['>', '='] }, associativity: 'right', precedence: 243, }, { name: 'GreaterEqual', trigger: { infix: '\\geqslant' }, associativity: 'right', precedence: 265, // Note: different precendence than `>=` as per MathML }, { name: 'GreaterNotEqual', trigger: { infix: '\\gneqq' }, associativity: 'right', precedence: 260, }, { name: 'NotGreaterNotEqual', trigger: { infix: '\\ngeqq' }, associativity: 'right', precedence: 260, }, { name: 'Greater', trigger: { infix: '>' }, associativity: 'right', precedence: 245, }, { name: 'Greater', trigger: { infix: '\\gt' }, associativity: 'right', precedence: 245, }, { name: 'NotGreater', trigger: { infix: '\\ngtr' }, associativity: 'right', precedence: 244, }, { name: 'NotGreater', trigger: { infix: ['!', '>'] }, associativity: 'right', precedence: 244, }, { name: 'RingEqual', trigger: { infix: '\\circeq' }, associativity: 'right', precedence: 260, }, { name: 'TriangleEqual', trigger: { infix: '\\triangleq' }, associativity: 'right', precedence: 260, }, { name: 'DotEqual', trigger: { infix: '\\doteq' }, associativity: 'right', precedence: 265, }, { name: 'DotEqualDot', trigger: { infix: '\\doteqdot' }, associativity: 'right', precedence: 265, }, { name: 'FallingDotEqual', trigger: { infix: '\\fallingdotseq' }, associativity: 'right', precedence: 265, }, { name: 'RisingDotEqual', trigger: { infix: '\\fallingdotseq' }, associativity: 'right', precedence: 265, }, { name: 'QuestionEqual', trigger: { infix: '\\questeq' }, associativity: 'right', precedence: 260, }, { name: 'Equivalent', trigger: { infix: '\\equiv' }, associativity: 'right', precedence: 260, }, { name: 'MuchLess', trigger: { infix: '\\ll' }, associativity: 'right', precedence: 260, }, { name: 'MuchGreater', trigger: { infix: '\\gg' }, associativity: 'right', precedence: 260, }, { name: 'Precedes', trigger: { infix: '\\prec' }, associativity: 'right', precedence: 260, }, { name: 'Succeeds', trigger: { infix: '\\succ' }, associativity: 'right', precedence: 260, }, { name: 'PrecedesEqual', trigger: { infix: '\\preccurlyeq' }, associativity: 'right', precedence: 260, }, { name: 'SucceedsEqual', trigger: { infix: '\\curlyeqprec' }, associativity: 'right', precedence: 260, }, { name: 'NotPrecedes', trigger: { infix: '\\nprec' }, associativity: 'right', precedence: 260, }, { name: 'NotSucceeds', trigger: { infix: '\\nsucc' }, associativity: 'right', precedence: 260, }, { name: 'Between', trigger: { infix: '\\between' }, associativity: 'right', precedence: 265, }, ]; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; var decimal = {exports: {}}; (function (module) { (function (globalScope) { /* * decimal.js v10.2.1 * An arbitrary-precision Decimal type for JavaScript. * https://github.com/MikeMcl/decimal.js * Copyright (c) 2020 Michael Mclaughlin <M8ch88l@gmail.com> * MIT Licence */ // ----------------------------------- EDITABLE DEFAULTS ------------------------------------ // // The maximum exponent magnitude. // The limit on the value of `toExpNeg`, `toExpPos`, `minE` and `maxE`. var EXP_LIMIT = 9e15, // 0 to 9e15 // The limit on the value of `precision`, and on the value of the first argument to // `toDecimalPlaces`, `toExponential`, `toFixed`, `toPrecision` and `toSignificantDigits`. MAX_DIGITS = 1e9, // 0 to 1e9 // Base conversion alphabet. NUMERALS = '0123456789abcdef', // The natural logarithm of 10 (1025 digits). LN10 = '2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862486334095254650828067566662873690987816894829072083255546808437998948262331985283935053089653777326288461633662222876982198867465436674744042432743651550489343149393914796194044002221051017141748003688084012647080685567743216228355220114804663715659121373450747856947683463616792101806445070648000277502684916746550586856935673420670581136429224554405758925724208241314695689016758940256776311356919292033376587141660230105703089634572075440370847469940168269282808481184289314848524948644871927809676271275775397027668605952496716674183485704422507197965004714951050492214776567636938662976979522110718264549734772662425709429322582798502585509785265383207606726317164309505995087807523710333101197857547331541421808427543863591778117054309827482385045648019095610299291824318237525357709750539565187697510374970888692180205189339507238539205144634197265287286965110862571492198849978748873771345686209167058', // Pi (1025 digits). PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632789', // The initial configuration properties of the Decimal constructor. DEFAULTS = { // These values must be integers within the stated ranges (inclusive). // Most of these values can be changed at run-time using the `Decimal.config` method. // The maximum number of significant digits of the result of a calculation or base conversion. // E.g. `Decimal.config({ precision: 20 });` precision: 20, // 1 to MAX_DIGITS // The rounding mode used when rounding to `precision`. // // ROUND_UP 0 Away from zero. // ROUND_DOWN 1 Towards zero. // ROUND_CEIL 2 Towards +Infinity. // ROUND_FLOOR 3 Towards -Infinity. // ROUND_HALF_UP 4 Towards nearest neighbour. If equidistant, up. // ROUND_HALF_DOWN 5 Towards nearest neighbour. If equidistant, down. // ROUND_HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour. // ROUND_HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity. // ROUND_HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity. // // E.g. // `Decimal.rounding = 4;` // `Decimal.rounding = Decimal.ROUND_HALF_UP;` rounding: 4, // 0 to 8 // The modulo mode used when calculating the modulus: a mod n. // The quotient (q = a / n) is calculated according to the corresponding rounding mode. // The remainder (r) is calculated as: r = a - n * q. // // UP 0 The remainder is positive if the dividend is negative, else is negative. // DOWN 1 The remainder has the same sign as the dividend (JavaScript %). // FLOOR 3 The remainder has the same sign as the divisor (Python %). // HALF_EVEN 6 The IEEE 754 remainder function. // EUCLID 9 Euclidian division. q = sign(n) * floor(a / abs(n)). Always positive. // // Truncated division (1), floored division (3), the IEEE 754 remainder (6), and Euclidian // division (9) are commonly used for the modulus operation. The other rounding modes can also // be used, but they may not give useful results. modulo: 1, // 0 to 9 // The exponent value at and beneath which `toString` returns exponential notation. // JavaScript numbers: -7 toExpNeg: -7, // 0 to -EXP_LIMIT // The exponent value at and above which `toString` returns exponential notation. // JavaScript numbers: 21 toExpPos: 21, // 0 to EXP_LIMIT // The minimum exponent value, beneath which underflow to zero occurs. // JavaScript numbers: -324 (5e-324) minE: -EXP_LIMIT, // -1 to -EXP_LIMIT // The maximum exponent value, above which overflow to Infinity occurs. // JavaScript numbers: 308 (1.7976931348623157e+308) maxE: EXP_LIMIT, // 1 to EXP_LIMIT // Whether to use cryptographically-secure random number generation, if available. crypto: false // true/false }, // ----------------------------------- END OF EDITABLE DEFAULTS ------------------------------- // Decimal, inexact, noConflict, quadrant, external = true, decimalError = '[DecimalError] ', invalidArgument = decimalError + 'Invalid argument: ', precisionLimitExceeded = decimalError + 'Precision limit exceeded', cryptoUnavailable = decimalError + 'crypto unavailable', mathfloor = Math.floor, mathpow = Math.pow, isBinary = /^0b([01]+(\.[01]*)?|\.[01]+)(p[+-]?\d+)?$/i, isHex = /^0x([0-9a-f]+(\.[0-9a-f]*)?|\.[0-9a-f]+)(p[+-]?\d+)?$/i, isOctal = /^0o([0-7]+(\.[0-7]*)?|\.[0-7]+)(p[+-]?\d+)?$/i, isDecimal = /^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i, BASE = 1e7, LOG_BASE = 7, MAX_SAFE_INTEGER = 9007199254740991, LN10_PRECISION = LN10.length - 1, PI_PRECISION = PI.length - 1, // Decimal.prototype object P = { name: '[object Decimal]' }; // Decimal prototype methods /* * absoluteValue abs * ceil * comparedTo cmp * cosine cos * cubeRoot cbrt * decimalPlaces dp * dividedBy div * dividedToIntegerBy divToInt * equals eq * floor * greaterThan gt * greaterThanOrEqualTo gte * hyperbolicCosine cosh * hyperbolicSine sinh * hyperbolicTangent tanh * inverseCosine acos * inverseHyperbolicCosine acosh * inverseHyperbolicSine asinh * inverseHyperbolicTangent atanh * inverseSine asin * inverseTangent atan * isFinite * isInteger isInt * isNaN * isNegative isNeg * isPositive isPos * isZero * lessThan lt * lessThanOrEqualTo lte * logarithm log * [maximum] [max] * [minimum] [min] * minus sub * modulo mod * naturalExponential exp * naturalLogarithm ln * negated neg * plus add * precision sd * round * sine sin * squareRoot sqrt * tangent tan * times mul * toBinary * toDecimalPlaces toDP * toExponential * toFixed * toFraction * toHexadecimal toHex * toNearest * toNumber * toOctal * toPower pow * toPrecision * toSignificantDigits toSD * toString * truncated trunc * valueOf toJSON */ /* * Return a new Decimal whose value is the absolute value of this Decimal. * */ P.absoluteValue = P.abs = function () { var x = new this.constructor(this); if (x.s < 0) x.s = 1; return finalise(x); }; /* * Return a new Decimal whose value is the value of this Decimal rounded to a whole number in the * direction of positive Infinity. * */ P.ceil = function () { return finalise(new this.constructor(this), this.e + 1, 2); }; /* * Return * 1 if the value of this Decimal is greater than the value of `y`, * -1 if the value of this Decimal is less than the value of `y`, * 0 if they have the same value, * NaN if the value of either Decimal is NaN. * */ P.comparedTo = P.cmp = function (y) { var i, j, xdL, ydL, x = this, xd = x.d, yd = (y = new x.constructor(y)).d, xs = x.s, ys = y.s; // Either NaN or ±Infinity? if (!xd || !yd) { return !xs || !ys ? NaN : xs !== ys ? xs : xd === yd ? 0 : !xd ^ xs < 0 ? 1 : -1; } // Either zero? if (!xd[0] || !yd[0]) return xd[0] ? xs : yd[0] ? -ys : 0; // Signs differ? if (xs !== ys) return xs; // Compare exponents. if (x.e !== y.e) return x.e > y.e ^ xs < 0 ? 1 : -1; xdL = xd.length; ydL = yd.length; // Compare digit by digit. for (i = 0, j = xdL < ydL ? xdL : ydL; i < j; ++i) { if (xd[i] !== yd[i]) return xd[i] > yd[i] ^ xs < 0 ? 1 : -1; } // Compare lengths. return xdL === ydL ? 0 : xdL > ydL ^ xs < 0 ? 1 : -1; }; /* * Return a new Decimal whose value is the cosine of the value in radians of this Decimal. * * Domain: [-Infinity, Infinity] * Range: [-1, 1] * * cos(0) = 1 * cos(-0) = 1 * cos(Infinity) = NaN * cos(-Infinity) = NaN * cos(NaN) = NaN * */ P.cosine = P.cos = function () { var pr, rm, x = this, Ctor = x.constructor; if (!x.d) return new Ctor(NaN); // cos(0) = cos(-0) = 1 if (!x.d[0]) return new Ctor(1); pr = Ctor.precision; rm = Ctor.rounding; Ctor.precision = pr + Math.max(x.e, x.sd()) + LOG_BASE; Ctor.rounding = 1; x = cosine(Ctor, toLessThanHalfPi(Ctor, x)); Ctor.precision = pr; Ctor.rounding = rm; return finalise(quadrant == 2 || quadrant == 3 ? x.neg() : x, pr, rm, true); }; /* * * Return a new Decimal whose value is the cube root of the value of this Decimal, rounded to * `precision` significant digits using rounding mode `rounding`. * * cbrt(0) = 0 * cbrt(-0) = -0 * cbrt(1) = 1 * cbrt(-1) = -1 * cbrt(N) = N * cbrt(-I) = -I * cbrt(I) = I * * Math.cbrt(x) = (x < 0 ? -Math.pow(-x, 1/3) : Math.pow(x, 1/3)) * */ P.cubeRoot = P.cbrt = function () { var e, m, n, r, rep, s, sd, t, t3, t3plusx, x = this, Ctor = x.constructor; if (!x.isFinite() || x.isZero()) return new Ctor(x); external = false; // Initial estimate. s = x.s * mathpow(x.s * x, 1 / 3); // Math.cbrt underflow/overflow? // Pass x to Math.pow as integer, then adjust the exponent of the result. if (!s || Math.abs(s) == 1 / 0) { n = digitsToString(x.d); e = x.e; // Adjust n exponent so it is a multiple of 3 away from x exponent. if (s = (e - n.length + 1) % 3) n += (s == 1 || s == -2 ? '0' : '00'); s = mathpow(n, 1 / 3); // Rarely, e may be one less than the result exponent value. e = mathfloor((e + 1) / 3) - (e % 3 == (e < 0 ? -1 : 2)); if (s == 1 / 0) { n = '5e' + e; } else { n = s.toExponential(); n = n.slice(0, n.indexOf('e') + 1) + e; } r = new Ctor(n); r.s = x.s; } else { r = new Ctor(s.toString()); } sd = (e = Ctor.precision) + 3; // Halley's method. // TODO? Compare Newton's method. for (;;) { t = r; t3 = t.times(t).times(t); t3plusx = t3.plus(x); r = divide(t3plusx.plus(x).times(t), t3plusx.plus(t3), sd + 2, 1); // TODO? Replace with for-loop and checkRoundingDigits. if (digitsToString(t.d).slice(0, sd) === (n = digitsToString(r.d)).slice(0, sd)) { n = n.slice(sd - 3, sd + 1); // The 4th rounding digit may be in error by -1 so if the 4 rounding digits are 9999 or 4999 // , i.e. approaching a rounding boundary, continue the iteration. if (n == '9999' || !rep && n == '4999') { // On the first iteration only, check to see if rounding up gives the exact result as the // nines may infinitely repeat. if (!rep) { finalise(t, e + 1, 0); if (t.times(t).times(t).eq(x)) { r = t; break; } } sd += 4; rep = 1; } else { // If the rounding digits are null, 0{0,4} or 50{0,3}, check for an exact result. // If not, then there are further digits and m will be truthy. if (!+n || !+n.slice(1) && n.charAt(0) == '5') { // Truncate to the first rounding digit. finalise(r, e + 1, 1); m = !r.times(r).times(r).eq(x); } break; } } } external = true; return finalise(r, e, Ctor.rounding, m); }; /* * Return the number of decimal places of the value of this Decimal. * */ P.decimalPlaces = P.dp = function () { var w, d = this.d, n = NaN; if (d) { w = d.length - 1; n = (w - mathfloor(this.e / LOG_BASE)) * LOG_BASE; // Subtract the number of trailing zeros of the last word. w = d[w]; if (w) for (; w % 10 == 0; w /= 10) n--; if (n < 0) n = 0; } return n; }; /* * n / 0 = I * n / N = N * n / I = 0 * 0 / n = 0 * 0 / 0 = N * 0 / N = N * 0 / I = 0 * N / n = N * N / 0 = N * N / N = N * N / I = N * I / n = I * I / 0 = I * I / N = N * I / I = N * * Return a new Decimal whose value is the value of this Decimal divided by `y`, rounded to * `precision` significant digits using rounding mode `rounding`. * */ P.dividedBy = P.div = function (y) { return divide(this, new this.constructor(y)); }; /* * Return a new Decimal whose value is the integer part of dividing the value of this Decimal * by the value of `y`, rounded to `precision` significant digits using rounding mode `rounding`. * */ P.dividedToIntegerBy = P.divToInt = function (y) { var x = this, Ctor = x.constructor; return finalise(divide(x, new Ctor(y), 0, 1, 1), Ctor.precision, Ctor.rounding); }; /* * Return true if the value of this Decimal is equal to the value of `y`, otherwise return false. * */ P.equals = P.eq = function (y) { return this.cmp(y) === 0; }; /* * Return a new Decimal whose value is the value of this Decimal rounded to a whole number in the * direction of negative Infinity. * */ P.floor = function () { return finalise(new this.constructor(this), this.e + 1, 3); }; /* * Return true if the value of this Decimal is greater than the value of `y`, otherwise return * false. * */ P.greaterThan = P.gt = function (y) { return this.cmp(y) > 0; }; /* * Return true if the value of this Decimal is greater than or equal to the value of `y`, * otherwise return false. * */ P.greaterThanOrEqualTo = P.gte = function (y) { var k = this.cmp(y); return k == 1 || k === 0; }; /* * Return a new Decimal whose value is the hyperbolic cosine of the value in radians of this * Decimal. * * Domain: [-Infinity, Infinity] * Range: [1, Infinity] * * cosh(x) = 1 + x^2/2! + x^4/4! + x^6/6! + ... * * cosh(0) = 1 * cosh(-0) = 1 * cosh(Infinity) = Infinity * cosh(-Infinity) = Infinity * cosh(NaN) = NaN * * x time taken (ms) result * 1000 9 9.8503555700852349694e+433 * 10000 25 4.4034091128314607936e+4342 * 100000 171 1.4033316802130615897e+43429 * 1000000 3817 1.5166076984010437725e+434294 * 10000000 abandoned after 2 minute wait * * TODO? Compare performance of cosh(x) = 0.5 * (exp(x) + exp(-x)) * */ P.hyperbolicCosine = P.cosh = function () { var k, n, pr, rm, len, x = this, Ctor = x.constructor, one = new Ctor(1); if (!x.isFinite()) return new Ctor(x.s ? 1 / 0 : NaN); if (x.isZero()) return one; pr = Ctor.precision; rm = Ctor.rounding; Ctor.precision = pr + Math.max(x.e, x.sd()) + 4; Ctor.rounding = 1; len = x.d.length; // Argument reduction: cos(4x) = 1 - 8cos^2(x) + 8cos^4(x) + 1 // i.e. cos(x) = 1 - cos^2(x/4)(8 - 8cos^2(x/4)) // Estimate the optimum number of times to use the argument reduction. // TODO? Estimation reused from cosine() and may not be optimal here. if (len < 32) { k = Math.ceil(len / 3); n = (1 / tinyPow(4, k)).toString(); } else { k = 16; n = '2.3283064365386962890625e-10'; } x = taylorSeries(Ctor, 1, x.times(n), new Ctor(1), true); // Reverse argument reduction var cosh2_x, i = k, d8 = new Ctor(8); for (; i--;) { cosh2_x = x.times(x); x = one.minus(cosh2_x.times(d8.minus(cosh2_x.times(d8)))); } return finalise(x, Ctor.precision = pr, Ctor.rounding = rm, true); }; /* * Return a new Decimal whose value is the hyperbolic sine of the value in radians of this * Decimal. * * Domain: [-Infinity, Infinity] * Range: [-Infinity, Infinity] * * sinh(x) = x + x^3/3! + x^5/5! + x^7/7! + ... * * sinh(0) = 0 * sinh(-0) = -0 * sinh(Infinity) = Infinity * sinh(-Infinity) = -Infinity * sinh(NaN) = NaN * * x time taken (ms) * 10 2 ms * 100 5 ms * 1000 14 ms * 10000 82 ms * 100000 886 ms 1.4033316802130615897e+43429 * 200000 2613 ms * 300000 5407 ms * 400000 8824 ms * 500000 13026 ms 8.7080643612718084129e+217146 * 1000000 48543 ms * * TODO? Compare performance of sinh(x) = 0.5 * (exp(x) - exp(-x)) * */ P.hyperbolicSine = P.sinh = function () { var k, pr, rm, len, x = this, Ctor = x.constructor; if (!x.isFinite() || x.isZero()) return new Ctor(x); pr = Ctor.precision; rm = Ctor.rounding; Ctor.precision = pr + Math.max(x.e, x.sd()) + 4; Ctor.rounding = 1; len = x.d.length; if (len < 3) { x = taylorSeries(Ctor, 2, x, x, true); } else { // Alternative argument reduction: sinh(3x) = sinh(x)(3 + 4sinh^2(x)) // i.e. sinh(x) = sinh(x/3)(3 + 4sinh^2(x/3)) // 3 multiplications and 1 addition // Argument reduction: sinh(5x) = sinh(x)(5 + sinh^2(x)(20 + 16sinh^2(x))) // i.e. sinh(x) = sinh(x/5)(5 + sinh^2(x/5)(20 + 16sinh^2(x/5))) // 4 multiplications and 2 additions // Estimate the optimum number of times to use the argument reduction. k = 1.4 * Math.sqrt(len); k = k > 16 ? 16 : k | 0; x = x.times(1 / tinyPow(5, k)); x = taylorSeries(Ctor, 2, x, x, true); // Reverse argument reduction var sinh2_x, d5 = new Ctor(5), d16 = new Ctor(16), d20 = new Ctor(20); for (; k--;) { sinh2_x = x.times(x); x = x.times(d5.plus(sinh2_x.times(d16.times(sinh2_x).plus(d20)))); } } Ctor.precision = pr; Ctor.rounding = rm; return finalise(x, pr, rm, true); }; /* * Return a new Decimal whose value is the hyperbolic tangent of the value in radians of this * Decimal. * * Domain: [-Infinity, Infinity] * Range: [-1, 1] * * tanh(x) = sinh(x) / cosh(x) * * tanh(0) = 0 * tanh(-0) = -0 * tanh(Infinity) = 1 * tanh(-Infinity) = -1 * tanh(NaN) = NaN * */ P.hyperbolicTangent = P.tanh = function () { var pr, rm, x = this, Ctor = x.constructor; if (!x.isFinite()) return new Ctor(x.s); if (x.isZero()) return new Ctor(x); pr = Ctor.precision; rm = Ctor.rounding; Ctor.precision = pr + 7; Ctor.rounding = 1; return divide(x.sinh(), x.cosh(), Ctor.precision = pr, Ctor.rounding = rm); }; /* * Return a new Decimal whose value is the arccosine (inverse cosine) in radians of the value of * this Decimal. * * Domain: [-1, 1] * Range: [0, pi] * * acos(x) = pi/2 - asin(x) * * acos(0) = pi/2 * acos(-0) = pi/2 * acos(1) = 0 * acos(-1) = pi * acos(1/2) = pi/3 * acos(-1/2) = 2*pi/3 * acos(|x| > 1) = NaN * acos(NaN) = NaN * */ P.inverseCosine = P.acos = function () { var halfPi, x = this, Ctor = x.constructor, k = x.abs().cmp(1), pr = Ctor.precision, rm = Ctor.rounding; if (k !== -1) { return k === 0 // |x| is 1 ? x.isNeg() ? getPi(Ctor, pr, rm) : new Ctor(0) // |x| > 1 or x is NaN : new Ctor(NaN); } if (x.isZero()) return getPi(Ctor, pr + 4, rm).times(0.5); // TODO? Special case acos(0.5) = pi/3 and acos(-0.5) = 2*pi/3 Ctor.precision = pr + 6; Ctor.rounding = 1; x = x.asin(); halfPi = getPi(Ctor, pr + 4, rm).times(0.5); Ctor.precision = pr; Ctor.rounding = rm; return halfPi.minus(x); }; /* * Return a new Decimal whose value is the inverse of the hyperbolic cosine in radians of the * value of this Decimal. * * Domain: [1, Infinity] * Range: [0, Infinity] * * acosh(x) = ln(x + sqrt(x^2 - 1)) * * acosh(x < 1) = NaN * acosh(NaN) = NaN * acosh(Infinity) = Infinity * acosh(-Infinity) = NaN * acosh(0) = NaN * acosh(-0) = NaN * acosh(1) = 0 * acosh(-1) = NaN * */ P.inverseHyperbolicCosine = P.acosh = function () { var pr, rm, x = this, Ctor = x.constructor; if (x.lte(1)) return new Ctor(x.eq(1) ? 0 : NaN); if (!x.isFinite()) return new Ctor(x); pr = Ctor.precision; rm = Ctor.rounding; Ctor.precision = pr + Math.max(Math.abs(x.e), x.sd()) + 4; Ctor.rounding = 1; external = false; x = x.times(x).minus(1).sqrt().plus(x); external = true; Ctor.precision = pr; Ctor.rounding = rm; return x.ln(); }; /* * Return a new Decimal whose value is the inverse of the hyperbolic sine in radians of the value * of this Decimal. * * Domain: [-Infinity, Infinity] * Range: [-Infinity, Infinity] * * asinh(x) = ln(x + sqrt(x^2 + 1)) * * asinh(NaN) = NaN * asinh(Infinity) = Infinity * asinh(-Infinity) = -Infinity * asinh(0) = 0 * asinh(-0) = -0 * */ P.i