@cortex-js/math-json
Version:
A JSON schema to represent math formulas
1,475 lines (1,351 loc) • 378 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MathJson = {}));
}(this, (function (exports) { 'use strict';
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
*