UNPKG

bigfloat-esnext

Version:

A library for arbitrary precision floating point arithmetic.

183 lines (182 loc) 6.62 kB
import { add, div, exponentiation, mul, sub } from "./arithmetic.js"; import { PRECISION, ZERO } from "./constants.js"; import { make, normalize, string } from "./constructors.js"; import { is_number, is_zero } from "./predicates.js"; import { eq, gt, lt } from "./relational.js"; export function evaluate(source, precision = PRECISION) { if (typeof source !== "string") { throw Error("The first parameter was expected to be a string."); } // This function relies on an algorithm that fully parenthesizes the expression function parenthesize(expr) { return "((((" + expr .replace(/\(/g, "((((") .replace(/\)/g, "))))") .replace(/(^|[^!])===?/g, ")))==(((") .replace(/<=/g, ")))<=(((") .replace(/>=/g, ")))>=(((") .replace(/<(?!=)/g, ")))<(((") .replace(/>(?!=)/g, ")))>(((") .replace(/!==?/g, ")))!=(((") .replace(/(^|[^e])\+/g, "))+((") .replace(/(^|[^e])-(?!\d)/g, "))-((") .replace(/\^|\*\*/g, "**") .replace(/(^|[^*])\*(?!\*)/g, ")*(") .replace(/\//g, ")/(") .replace(/%/g, ")%(") .replace(/ /g, "") + "))))"; } const expression = parenthesize(source); const rx_tokens = /(-?\d+(?:\.\d+)?(?:e(-?|\+?)\d+)?)|(\(|\))|(\+|-|\/|\*\*|==|!=|<=?|>=?|\*|\^|%)/g; // Capture groups // [1] Number // [2] Paren // [3] Operator // Tokenize the expression const tokens = (expression.match(rx_tokens) || []).map(function (element) { const parens = ["(", ")"]; const operators = [ "+", "-", "*", "**", "/", "%", "==", "!=", "<", ">", "<=", ">=" ]; if (element === "%") { throw Error("The modulo operator is not supported yet."); } if (parens.includes(element)) { return { type: "paren", value: element }; } else if (operators.includes(element)) { return { type: "operator", value: element }; } else if (is_number(element)) { return { type: "number", value: normalize(make(element.replace("+", ""))) }; } throw Error(`Unexpected token "${element}"`); }); let n = 0; tokens.forEach(function (element, index) { if (element.value === "**") { if (tokens[index + 2].value === "**") { tokens.splice(index + 1, 0, { value: "(", type: "paren" }); n += 1; } else { while (n) { n -= 1; tokens.splice(index + 3, 0, { value: ")", type: "paren" }); } } } }); // Recursively resolve the parentheses function resolve(arr) { // Remove parens when there's only one value if (arr.length <= 3) { return [arr[1]]; } let last_left_paren; let i = 0; while (i <= arr.length) { if (typeof last_left_paren === "number") { const { value } = arr[i]; if (value === "(") { last_left_paren = i; } if (value === ")") { const start = arr.slice(0, last_left_paren); const term = arr.splice(last_left_paren, i - last_left_paren + 1); const end = arr.slice(last_left_paren, arr.length); return resolve([...start, ...resolve(term), ...end]); } if (arr[i].type === "operator" && arr[i + 1].type !== "paren") { const start = arr.slice(0, arr[i + 2].type === "operator" || arr[i + 2].type === "paren" ? last_left_paren + 1 : last_left_paren); const ops = arr.splice(i - 1, 3); const end = arr.slice(arr[i - 1].type === "operator" || (arr[i + 1] || {}).type === "paren" || i >= arr.length ? i - 1 : i, arr.length); const a = ops[0].value; const b = ops[2].value; const operator = ops[1].value; const bigfloat_return = { "+"() { return add(a, b); }, "-"() { return sub(a, b); }, "*"() { return mul(a, b); }, "/"() { return is_zero(b) ? ZERO : div(a, b, precision); }, "**"() { return exponentiation(a, b); } }[operator]; const boolean_return = { "=="() { return eq(a, b); }, "!="() { return !eq(a, b); }, "<"() { return lt(a, b); }, ">"() { return gt(a, b); }, "<="() { return lt(a, b) || eq(a, b); }, ">="() { return gt(a, b) || eq(a, b); } }[operator]; const res = bigfloat_return ? { type: "number", value: bigfloat_return() } : { type: "boolean", value: boolean_return() }; return resolve([...start, res, ...end]); } } i += 1; } } const [result] = resolve(tokens); if (result.type === "number") { return string(result.value); } return result.value; }