diff --git a/calc.js b/calc.js index 7e95dab..6531e87 100644 --- a/calc.js +++ b/calc.js @@ -1,217 +1,231 @@ -// Copyright (c) 2019 Oliver Lau , Heise Medien GmbH & Co. KG +// Copyright (c) 2019-2024 Oliver Lau , Heise Medien GmbH & Co. KG +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + (function (window) { - 'use strict'; - - let inputPaneEl = null; - let msgEl = null; - let msgContainerEl = null; - let overlayEl = null; - let outputPaneEl = null; - let loaderIconEl = null; - let baseFormEl = null; - let base = localStorage.getItem('base') || 2; - let results = []; - let numberCruncher = null; - let t0Post = 0; - let isCalculating = false; - let workerFile = 'numbercruncher-bigint.js'; - - const formulaChanged = () => { - if (isCalculating) { - numberCruncher.terminate(); - initWorker(); - } - msgEl.innerHTML = 'Calculating …'; - msgContainerEl.classList.remove('error'); - outputPaneEl.classList.add('greyout'); - loaderIconEl.classList.remove('hidden'); - const expressions = inputPaneEl.innerText.split('\n'); - t0Post = Date.now(); - isCalculating = true; - numberCruncher.postMessage({ - expressions: expressions, - base: base - }); - fitOutputPane(); - }; - - const baseChanged = event => { - base = parseInt(event.target.value); - localStorage.setItem('base', base); - formulaChanged(); - }; - - const msToStr = ms => { - if (ms < 1) { - return `<1ms`; - } - if (ms < 1000) { - return `${ms.toFixed(0)}ms` - } - return `${(1e-3 * ms).toFixed(1)}s`; - }; - - const visualResultFrom = result => { - const containerEl = document.createElement('div'); - containerEl.classList.add('result-container'); - const exprEl = document.createElement('span'); - exprEl.classList.add('expression'); - exprEl.innerHTML = `${result.expression} → `; - const resultEl = document.createElement('span'); - resultEl.classList.add('result'); - resultEl.innerHTML = result.result; - containerEl.appendChild(exprEl); - containerEl.appendChild(resultEl); - return containerEl; - }; - - const numberCruncherReady = msg => { - isCalculating = false; - if (msg.data.error) { - msgEl.innerHTML = msg.data.error; - msgContainerEl.classList.add('error'); - loaderIconEl.classList.add('hidden'); - } - else if (msg.data.results) { - msgContainerEl.classList.remove('error'); - outputPaneEl.classList.remove('greyout'); - results = msg.data.results; - msgEl.innerHTML = ''; - const dtPost = Date.now() - t0Post - msg.data.dtCalc - msg.data.dtRender; - outputPaneEl.innerHTML = ''; - if (results && results.length > 0) { - for (const result of results) { - outputPaneEl.appendChild(visualResultFrom(result)) + 'use strict'; + + const workerFile = 'numbercruncher.js'; + + let inputPaneEl = null; + let msgEl = null; + let msgContainerEl = null; + let overlayEl = null; + let outputPaneEl = null; + let loaderIconEl = null; + let baseFormEl = null; + let base = localStorage.getItem('base') || 2; + let results = []; + let numberCruncher = null; + let t0Post = 0; + let isCalculating = false; + + const formulaChanged = () => { + if (isCalculating) { + numberCruncher.terminate(); + initWorker(); + } + msgEl.innerHTML = 'Calculating …'; + msgContainerEl.classList.remove('error'); + outputPaneEl.classList.add('greyout'); + loaderIconEl.classList.remove('hidden'); + const expressions = inputPaneEl.innerText.split('\n'); + t0Post = Date.now(); + isCalculating = true; + numberCruncher.postMessage({ + expressions: expressions, + base: base + }); + fitOutputPane(); + }; + + const baseChanged = event => { + base = parseInt(event.target.value); + localStorage.setItem('base', base); + formulaChanged(); + }; + + const msToStr = ms => { + if (ms < 1) { + return `<1ms`; + } + if (ms < 1000) { + return `${ms.toFixed(0)}ms` + } + return `${(1e-3 * ms).toFixed(1)}s`; + }; + + const visualResultFrom = result => { + const containerEl = document.createElement('div'); + containerEl.classList.add('result-container'); + const exprEl = document.createElement('span'); + exprEl.classList.add('expression'); + exprEl.innerHTML = `${result.expression} → `; + const resultEl = document.createElement('span'); + resultEl.classList.add('result'); + resultEl.innerHTML = result.result; + containerEl.appendChild(exprEl); + containerEl.appendChild(resultEl); + return containerEl; + }; + + const numberCruncherReady = msg => { + isCalculating = false; + if (msg.data.error) { + msgEl.innerHTML = msg.data.error; + msgContainerEl.classList.add('error'); + loaderIconEl.classList.add('hidden'); } - msgEl.innerHTML = `${msToStr(msg.data.dtCalc)} to calculate, + else if (msg.data.results) { + msgContainerEl.classList.remove('error'); + outputPaneEl.classList.remove('greyout'); + results = msg.data.results; + msgEl.innerHTML = ''; + const dtPost = Date.now() - t0Post - msg.data.dtCalc - msg.data.dtRender; + outputPaneEl.innerHTML = ''; + if (results && results.length > 0) { + for (const result of results) { + outputPaneEl.appendChild(visualResultFrom(result)) + } + msgEl.innerHTML = `${msToStr(msg.data.dtCalc)} to calculate, ${msToStr(dtPost)} to transfer, ${msToStr(msg.data.dtRender)} to convert to base ${base}.`; - } - loaderIconEl.classList.add('hidden'); - } - }; - - const overlayKeyDown = event => { - if (event.key === 'Escape') { - overlayEl.classList.add('hidden'); - } - }; - - const initWorker = () => { - numberCruncher = new Worker(workerFile); - numberCruncher.onmessage = numberCruncherReady; - }; - - const terminateWorker = () => { - if (isCalculating) { - isCalculating = false; - numberCruncher.terminate(); - numberCruncher = new Worker('numbercruncher.js'); - loaderIconEl.classList.add('hidden'); - } - }; - - const toggleHelp = () => { - if (overlayEl.classList.contains('hidden')) { - overlayEl.classList.remove('hidden'); - document.getElementById('help-back').addEventListener('click', () => { - overlayEl.classList.add('hidden'); - window.removeEventListener('keydown', overlayKeyDown, { once: true, capture: true }); - }, { once: true, capture: true }); - window.addEventListener('keydown', overlayKeyDown, { once: true, capture: true }); - } - else { - overlayEl.classList.add('hidden'); - } - }; - - const onKeyUp = event => { - switch (event.key) { - case 'F1': - toggleHelp(); - break; - case 'Escape': - terminateWorker(); - break; - } - }; - - const onKeyDown = event => { - switch (event.key) { - case 'v': - if (typeof navigator.clipboard.readText === 'function' && (event.metaKey || event.ctrlKey)) { - navigator.clipboard.readText() - .then(clipboardText => { - document.execCommand('insertText', false, clipboardText); + } + loaderIconEl.classList.add('hidden'); + } + }; + + const overlayKeyDown = event => { + if (event.key === 'Escape') { + overlayEl.classList.add('hidden'); + } + }; + + const initWorker = () => { + numberCruncher = new Worker(workerFile); + numberCruncher.onmessage = numberCruncherReady; + }; + + const terminateWorker = () => { + if (isCalculating) { + isCalculating = false; + numberCruncher.terminate(); + numberCruncher = new Worker('numbercruncher.js'); + loaderIconEl.classList.add('hidden'); + } + }; + + const toggleHelp = () => { + if (overlayEl.classList.contains('hidden')) { + overlayEl.classList.remove('hidden'); + document.getElementById('help-back').addEventListener('click', () => { + overlayEl.classList.add('hidden'); + window.removeEventListener('keydown', overlayKeyDown, { once: true, capture: true }); + }, { once: true, capture: true }); + window.addEventListener('keydown', overlayKeyDown, { once: true, capture: true }); + } + else { + overlayEl.classList.add('hidden'); + } + }; + + const onKeyUp = event => { + switch (event.key) { + case 'F1': + toggleHelp(); + break; + case 'Escape': + terminateWorker(); + break; + } + }; + + const onKeyDown = event => { + switch (event.key) { + case 'v': + if (typeof navigator.clipboard.readText === 'function' && (event.metaKey || event.ctrlKey)) { + navigator.clipboard.readText() + .then(clipboardText => { + document.execCommand('insertText', false, clipboardText); + }); + event.stopPropagation(); + event.preventDefault(); + } + break; + default: + break; + } + return false; + }; + + const fitOutputPane = () => { + const h = document.getElementById('header').offsetHeight + + document.getElementById('msg-container').offsetHeight + + document.getElementById('input-pane').offsetHeight + + document.getElementById('base-selector').offsetHeight + + document.getElementById('footer').offsetHeight + + 8 * 4 - 1; + outputPaneEl.style.height = `${window.innerHeight - h}px`; + }; + + const onResize = () => { + fitOutputPane(); + }; + + const init = () => { + try { + const x = BigInt(0); + } + catch (e) { + console.error('BigInt not supported.'); + } + overlayEl = document.getElementById('overlay'); + loaderIconEl = document.getElementById('loader-icon'); + inputPaneEl = document.getElementById('input-pane'); + inputPaneEl.addEventListener('input', formulaChanged); + outputPaneEl = document.getElementById('output-pane'); + msgContainerEl = document.getElementById('msg-container'); + msgEl = document.getElementById('msg'); + msgEl.innerHTML = ''; + baseFormEl = document.getElementById('base-form'); + document.getElementById(`base-${base}`).checked = true; + baseFormEl.addEventListener('change', baseChanged); + initWorker(); + window.addEventListener('keyup', onKeyUp); + window.addEventListener('keydown', onKeyDown); + window.addEventListener('resize', onResize); + fetch('help.html') + .then(response => { + response.body.getReader().read() + .then(html => { + overlayEl.innerHTML = new TextDecoder('utf-8').decode(html.value); + }); + document.getElementById('help-button').addEventListener('click', toggleHelp); + }); + fitOutputPane(); + if (typeof navigator.clipboard.readText === 'undefined') { + inputPaneEl.addEventListener('paste', event => { + const clipboardData = event.clipboardData || window.clipboardData; + const pastedData = clipboardData.getData('Text'); + const parser = new DOMParser().parseFromString(pastedData, 'text/html'); + const clipboardText = parser.body.textContent || ''; + document.execCommand('insertText', false, clipboardText); + event.preventDefault(); + event.stopPropagation(); }); - event.stopPropagation(); - event.preventDefault(); } - break; - default: - break; - } - return false; - }; - - const fitOutputPane = () => { - const h = document.getElementById('header').offsetHeight - + document.getElementById('msg-container').offsetHeight - + document.getElementById('input-pane').offsetHeight - + document.getElementById('base-selector').offsetHeight - + document.getElementById('footer').offsetHeight - + 8 * 4 - 1; - outputPaneEl.style.height = `${window.innerHeight - h}px`; - }; - - const onResize = () => { - fitOutputPane(); - }; - - const init = () => { - try { - const x = BigInt(0); - } - catch (e) { - workerFile = 'numbercruncher-jsbi.js'; - console.log('BigInt not supported. Falling back to JSBI.'); - } - overlayEl = document.getElementById('overlay'); - loaderIconEl = document.getElementById('loader-icon'); - inputPaneEl = document.getElementById('input-pane'); - inputPaneEl.addEventListener('input', formulaChanged); - outputPaneEl = document.getElementById('output-pane'); - msgContainerEl = document.getElementById('msg-container'); - msgEl = document.getElementById('msg'); - msgEl.innerHTML = ''; - baseFormEl = document.getElementById('base-form'); - document.getElementById(`base-${base}`).checked = true; - baseFormEl.addEventListener('change', baseChanged); - initWorker(); - window.addEventListener('keyup', onKeyUp); - window.addEventListener('keydown', onKeyDown); - window.addEventListener('resize', onResize); - fetch('help.html') - .then(response => { - response.body.getReader().read() - .then(html => { - overlayEl.innerHTML = new TextDecoder('utf-8').decode(html.value); - }); - document.getElementById('help-button').addEventListener('click', toggleHelp); - }); - fitOutputPane(); - if (typeof navigator.clipboard.readText === 'undefined') { - inputPaneEl.addEventListener('paste', event => { - const clipboardData = event.clipboardData || window.clipboardData; - const pastedData = clipboardData.getData('Text'); - const parser = new DOMParser().parseFromString(pastedData, 'text/html'); - const clipboardText = parser.body.textContent || ''; - document.execCommand('insertText', false, clipboardText); - event.preventDefault(); - event.stopPropagation(); - }); - } - }; - window.addEventListener('load', init); + }; + window.addEventListener('load', init); })(window); diff --git a/default.css b/default.css index bdfca35..3889b6a 100644 --- a/default.css +++ b/default.css @@ -1,210 +1,253 @@ * { - margin: 0; - padding: 0; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +@font-face { + font-family: 'Viktor Mono'; + src: url(static/fonts/WOFF2/VictorMono-Regular.woff2); +} + +@font-face { + font-family: 'Viktor Mono'; + font-weight: bold; + src: url(static/fonts/WOFF2/VictorMono-Bold.woff2); +} + +@font-face { + font-family: 'Viktor Mono'; + font-weight: bolder; + src: url(static/fonts/WOFF2/VictorMono-Medium.woff2); +} + +@font-face { + font-family: 'Viktor Mono'; + font-style: italic; + src: url(static/fonts/WOFF2/VictorMono-Italic.woff2); +} + +@font-face { + font-family: 'Viktor Mono'; + font-weight: bold; + font-style: italic; + src: url(static/fonts/WOFF2/VictorMono-BoldItalic.woff2); } html, body { - background-color: #e9e7de; - color: #222; - font-family: 'Saira Semi Condensed', sans-serif; - font-size: 12pt; + background-color: #e9e7de; + color: #222; + font-family: 'Viktor Mono', monospace; + font-size: 12pt; } h1 { - font-size: 18pt; + font-size: 18pt; } a { - color: #555; - font-weight: bold; - text-decoration: none; + color: #555; + font-style: italic; + text-decoration: none; } a:visited { - color: #666; + color: #666; } a:hover { - text-decoration: underline; + text-decoration: underline; } #overlay { - position: fixed; - width: 100%; - height: 100%; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0,0,0,0.5); - z-index: 2; + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 2; } input[type="radio"] { - margin-right: 0.2em; - cursor: pointer; + margin-right: 0.2em; + cursor: pointer; } -input[type="radio"] + label { - margin-right: 1em; - cursor: pointer; +input[type="radio"]+label { + margin-right: 1em; + cursor: pointer; } #help-button { - cursor: pointer; - margin: 4px 4px 0 0; - transition-property: transform; - transition-duration: 0.1s; - transition-timing-function: ease; - transition-delay: 0s; + cursor: pointer; + margin: 4px 4px 0 0; + transition-property: transform; + transition-duration: 0.1s; + transition-timing-function: ease; + transition-delay: 0s; } #help-button:hover { - transform: scale(1.15); + transform: scale(1.15); } .error { - background-color: rgb(223, 53, 53) !important; - font-weight: bold; + background-color: rgb(223, 53, 53) !important; + font-weight: bold; } .hidden { - display: none !important; + display: none !important; } .show { - display: block; + display: block; } #loader-icon { - display: inline-block; - width: 17px; - height: 17px; - margin-right: 8px; - vertical-align: middle; + display: inline-block; + width: 17px; + height: 17px; + margin-right: 8px; + vertical-align: middle; } .greyout { - opacity: 0.5; - background-color: rgba(0, 0, 0, 0.8) !important; + opacity: 0.5; + background-color: rgba(0, 0, 0, 0.8) !important; } table { - margin: 12pt 12pt 8pt 0; + margin: 12pt 12pt 8pt 0; } -table, td, th { - border-collapse: collapse; +table, +td, +th { + border-collapse: collapse; } -table > caption { - font-size: 12pt; - font-weight: bold; - text-align: left; - border: 1px solid #333; - color: #eee; - background-color: #333; - padding-left: 0.2em; +table>caption { + font-size: 12pt; + font-weight: bold; + text-align: left; + border: 1px solid #333; + color: #eee; + background-color: #333; + padding-left: 0.2em; } -td, th { - border: 1px solid #333; - padding: 0.2ex 0.4em 0.2ex 0.2em; +td, +th { + border: 1px solid #333; + padding: 0.2ex 0.4em 0.2ex 0.2em; } th { - text-align: left; + text-align: left; } tfoot td { - font-size: smaller; + font-size: smaller; } .app { - position: relative; - display: flex; - flex-flow: row wrap; - align-items: stretch; - height: 100%; + position: relative; + display: flex; + flex-flow: row wrap; + align-items: stretch; + height: 100%; } -.app > * { - padding: 4px; - flex-grow: 1; - flex-basis: 100%; +.app>* { + padding: 4px; + flex-grow: 1; + flex-basis: 100%; } -.header, .footer { - background-color: #333; - color: #eee; - vertical-align: middle; +.header, +.footer { + padding: 4px 8px; + background-color: #333; + color: #eee; + vertical-align: middle; + font-weight: bold; + font-size: smaller; } .header { - font-size: 15pt; - font-weight: bold; + font-size: 15pt; + font-weight: bold; } .footer a { - color: #ddd; + color: #ddd; } .footer a:visited { - color: #ccc; + color: #ccc; } .msg-container { - background-color:rgb(238, 143, 0); - color: white; - height: 3ex; - vertical-align: middle; + background-color: rgb(238, 143, 0); + color: white; + height: 4ex; + vertical-align: middle; + font-size: smaller; + font-weight: bolder; + padding: 3px 8px; } #msg { - vertical-align: middle; + vertical-align: middle; } -#output-pane, #input-pane { - background-color: white; - color: black; - font-family: 'Roboto Mono', monospace; - font-size: 12pt; - margin: 4pt; - border: 1px solid #444; +#output-pane, +#input-pane { + background-color: white; + color: black; + font-family: 'Viktor Mono', monospace; + font-size: 12pt; + margin: 4pt; + border: 1px solid #444; } #input-pane { - min-height: 4ex; - word-break: break-all; - overflow-y: auto; - width: auto; + min-height: 4ex; + word-break: break-all; + overflow-y: auto; + width: auto; } -#input-pane:empty:not(:focus):before, #output-pane:empty:not(:focus):before { - content: attr(data-text); - color: #999; +#input-pane:empty:not(:focus):before, +#output-pane:empty:not(:focus):before { + content: attr(data-text); + color: #999; } #output-pane { - word-break: break-all; - overflow-y: scroll; - width: auto; - height: 24px; + word-break: break-all; + overflow-y: scroll; + width: auto; + height: 24px; } .result-container { - margin-bottom: 4pt; + margin-bottom: 4pt; } .expression { - padding: 0 0.2em; - background-color:rgb(238, 143, 0); - color: white; - display: inline-block; + padding: 0 0.2em; + background-color: rgb(238, 143, 0); + color: white; + display: inline-block; } .result { - padding: 0 0.2em; - display: inline; -} + padding: 0 0.2em; + display: inline; +} \ No newline at end of file diff --git a/files-to-copy.txt b/files-to-copy.txt new file mode 100644 index 0000000..a8ce247 --- /dev/null +++ b/files-to-copy.txt @@ -0,0 +1,23 @@ +index.html +numbercruncher.js +calc.js +favicon.ico +help.html +default.css +worker-message-handler.js +icons/favicon-48.png +icons/favicon-57.png +icons/favicon-60.png +icons/favicon-72.png +icons/favicon-96.png +icons/favicon-114.png +icons/favicon-120.png +icons/favicon-144.png +icons/favicon-152.png +icons/favicon-167.png +icons/favicon-180.png +icons/favicon-196.png +icons/favicon-256.png +icons/favicon-384.png +icons/favicon-512.png +static/fonts/* \ No newline at end of file diff --git a/index.html b/index.html index 73fa57d..bece138 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,14 @@ - + + Arbitrary Precision Calculator - + + - @@ -26,12 +27,14 @@ +
- + \ No newline at end of file diff --git a/jsbi.js b/jsbi.js deleted file mode 100644 index b5d155d..0000000 --- a/jsbi.js +++ /dev/null @@ -1,1784 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the “License”); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// . -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an “AS IS” BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -class JSBI extends Array { - constructor(length, sign) { - if (length > JSBI.__kMaxLength) { - throw new RangeError('Maximum BigInt size exceeded'); - } - super(length); - this.sign = sign; - } - - static BigInt(arg) { - if (typeof arg === 'number') { - if (arg === 0) return JSBI.__zero(); - if ((arg | 0) === arg) { - if (arg < 0) { - return JSBI.__oneDigit(-arg, true); - } - return JSBI.__oneDigit(arg, false); - } - if (!Number.isFinite(arg) || Math.floor(arg) !== arg) { - throw new RangeError('The number ' + arg + ' cannot be converted to ' + - 'BigInt because it is not an integer'); - } - return JSBI.__fromDouble(arg); - } else if (typeof arg === 'string') { - const result = JSBI.__fromString(arg); - if (result === null) { - throw new SyntaxError('Cannot convert ' + arg + ' to a BigInt'); - } - return result; - } else if (typeof arg === 'boolean') { - if (arg === true) { - return JSBI.__oneDigit(1, false); - } - return JSBI.__zero(); - } else if (typeof arg === 'object') { - if (arg.constructor === JSBI) return arg; - const primitive = JSBI.__toPrimitive(arg); - return JSBI.BigInt(primitive); - } - throw new TypeError('Cannot convert ' + arg + ' to a BigInt'); - } - - toDebugString() { - const result = ['BigInt[']; - for (const digit of this) { - result.push((digit ? (digit >>> 0).toString(16) : digit) + ', '); - } - result.push(']'); - return result.join(''); - } - - toString(radix = 10) { - if (radix < 2 || radix > 36) { - throw new RangeError( - 'toString() radix argument must be between 2 and 36'); - } - if (this.length === 0) return '0'; - if ((radix & (radix - 1)) === 0) { - return JSBI.__toStringBasePowerOfTwo(this, radix); - } - return JSBI.__toStringGeneric(this, radix, false); - } - - // Equivalent of "Number(my_bigint)" in the native implementation. - static toNumber(x) { - const xLength = x.length; - if (xLength === 0) return 0; - if (xLength === 1) { - const value = x.__unsignedDigit(0); - return x.sign ? -value : value; - } - const xMsd = x.__digit(xLength - 1); - const msdLeadingZeros = Math.clz32(xMsd); - const xBitLength = xLength * 32 - msdLeadingZeros; - if (xBitLength > 1024) return x.sign ? -Infinity : Infinity; - let exponent = xBitLength - 1; - let currentDigit = xMsd; - let digitIndex = xLength - 1; - const shift = msdLeadingZeros + 1; - let mantissaHigh = (shift === 32) ? 0 : currentDigit << shift; - mantissaHigh >>>= 12; - const mantissaHighBitsUnset = shift - 12; - let mantissaLow = (shift >= 12) ? 0 : (currentDigit << (20 + shift)); - let mantissaLowBitsUnset = 20 + shift; - if (mantissaHighBitsUnset > 0 && digitIndex > 0) { - digitIndex--; - currentDigit = x.__digit(digitIndex); - mantissaHigh |= (currentDigit >>> (32 - mantissaHighBitsUnset)); - mantissaLow = currentDigit << mantissaHighBitsUnset; - mantissaLowBitsUnset = mantissaHighBitsUnset; - } - if (mantissaLowBitsUnset > 0 && digitIndex > 0) { - digitIndex--; - currentDigit = x.__digit(digitIndex); - mantissaLow |= (currentDigit >>> (32 - mantissaLowBitsUnset)); - mantissaLowBitsUnset -= 32; - } - const rounding = JSBI.__decideRounding(x, mantissaLowBitsUnset, - digitIndex, currentDigit); - if (rounding === 1 || (rounding === 0 && (mantissaLow & 1) === 1)) { - mantissaLow = (mantissaLow + 1) >>> 0; - if (mantissaLow === 0) { - // Incrementing mantissaLow overflowed. - mantissaHigh++; - if ((mantissaHigh >>> 20) !== 0) { - // Incrementing mantissaHigh overflowed. - mantissaHigh = 0; - exponent++; - if (exponent > 1023) { - // Incrementing the exponent overflowed. - return x.sign ? -Infinity : Infinity; - } - } - } - } - const signBit = x.sign ? (1 << 31) : 0; - exponent = (exponent + 0x3FF) << 20; - JSBI.__kBitConversionInts[1] = signBit | exponent | mantissaHigh; - JSBI.__kBitConversionInts[0] = mantissaLow; - return JSBI.__kBitConversionDouble[0]; - } - - // Operations. - - static unaryMinus(x) { - if (x.length === 0) return x; - const result = x.__copy(); - result.sign = !x.sign; - return result; - } - - static bitwiseNot(x) { - if (x.sign) { - // ~(-x) == ~(~(x-1)) == x-1 - return JSBI.__absoluteSubOne(x).__trim(); - } - // ~x == -x-1 == -(x+1) - return JSBI.__absoluteAddOne(x, true); - } - - static exponentiate(x, y) { - if (y.sign) { - throw new RangeError('Exponent must be positive'); - } - if (y.length === 0) { - return JSBI.__oneDigit(1, false); - } - if (x.length === 0) return x; - if (x.length === 1 && x.__digit(0) === 1) { - // (-1) ** even_number == 1. - if (x.sign && (y.__digit(0) & 1) === 0) { - return JSBI.unaryMinus(x); - } - // (-1) ** odd_number == -1, 1 ** anything == 1. - return x; - } - // For all bases >= 2, very large exponents would lead to unrepresentable - // results. - if (y.length > 1) throw new RangeError('BigInt too big'); - let expValue = y.__unsignedDigit(0); - if (expValue === 1) return x; - if (expValue >= JSBI.__kMaxLengthBits) { - throw new RangeError('BigInt too big'); - } - if (x.length === 1 && x.__digit(0) === 2) { - // Fast path for 2^n. - const neededDigits = 1 + (expValue >>> 5); - const sign = x.sign && ((expValue & 1) !== 0); - const result = new JSBI(neededDigits, sign); - result.__initializeDigits(); - // All bits are zero. Now set the n-th bit. - const msd = 1 << (expValue & 31); - result.__setDigit(neededDigits - 1, msd); - return result; - } - let result = null; - let runningSquare = x; - // This implicitly sets the result's sign correctly. - if ((expValue & 1) !== 0) result = x; - expValue >>= 1; - for (; expValue !== 0; expValue >>= 1) { - runningSquare = JSBI.multiply(runningSquare, runningSquare); - if ((expValue & 1) !== 0) { - if (result === null) { - result = runningSquare; - } else { - result = JSBI.multiply(result, runningSquare); - } - } - } - return result; - } - - static multiply(x, y) { - if (x.length === 0) return x; - if (y.length === 0) return y; - let resultLength = x.length + y.length; - if (x.__clzmsd() + y.__clzmsd() >= 32) { - resultLength--; - } - const result = new JSBI(resultLength, x.sign !== y.sign); - result.__initializeDigits(); - for (let i = 0; i < x.length; i++) { - JSBI.__multiplyAccumulate(y, x.__digit(i), result, i); - } - return result.__trim(); - } - - static divide(x, y) { - if (y.length === 0) throw new RangeError('Division by zero'); - if (JSBI.__absoluteCompare(x, y) < 0) return JSBI.__zero(); - const resultSign = x.sign !== y.sign; - const divisor = y.__unsignedDigit(0); - let quotient; - if (y.length === 1 && divisor <= 0xFFFF) { - if (divisor === 1) { - return resultSign === x.sign ? x : JSBI.unaryMinus(x); - } - quotient = JSBI.__absoluteDivSmall(x, divisor, null); - } else { - quotient = JSBI.__absoluteDivLarge(x, y, true, false); - } - quotient.sign = resultSign; - return quotient.__trim(); - } - - static remainder(x, y) { - if (y.length === 0) throw new RangeError('Division by zero'); - if (JSBI.__absoluteCompare(x, y) < 0) return x; - const divisor = y.__unsignedDigit(0); - if (y.length === 1 && divisor <= 0xFFFF) { - if (divisor === 1) return JSBI.__zero(); - const remainderDigit = JSBI.__absoluteModSmall(x, divisor); - if (remainderDigit === 0) return JSBI.__zero(); - return JSBI.__oneDigit(remainderDigit, x.sign); - } - const remainder = JSBI.__absoluteDivLarge(x, y, false, true); - remainder.sign = x.sign; - return remainder.__trim(); - } - - static add(x, y) { - const sign = x.sign; - if (sign === y.sign) { - // x + y == x + y - // -x + -y == -(x + y) - return JSBI.__absoluteAdd(x, y, sign); - } - // x + -y == x - y == -(y - x) - // -x + y == y - x == -(x - y) - if (JSBI.__absoluteCompare(x, y) >= 0) { - return JSBI.__absoluteSub(x, y, sign); - } - return JSBI.__absoluteSub(y, x, !sign); - } - - static subtract(x, y) { - const sign = x.sign; - if (sign !== y.sign) { - // x - (-y) == x + y - // (-x) - y == -(x + y) - return JSBI.__absoluteAdd(x, y, sign); - } - // x - y == -(y - x) - // (-x) - (-y) == y - x == -(x - y) - if (JSBI.__absoluteCompare(x, y) >= 0) { - return JSBI.__absoluteSub(x, y, sign); - } - return JSBI.__absoluteSub(y, x, !sign); - } - - static leftShift(x, y) { - if (y.length === 0 || x.length === 0) return x; - if (y.sign) return JSBI.__rightShiftByAbsolute(x, y); - return JSBI.__leftShiftByAbsolute(x, y); - } - - static signedRightShift(x, y) { - if (y.length === 0 || x.length === 0) return x; - if (y.sign) return JSBI.__leftShiftByAbsolute(x, y); - return JSBI.__rightShiftByAbsolute(x, y); - } - - static unsignedRightShift() { - throw new TypeError( - 'BigInts have no unsigned right shift; use >> instead'); - } - - static lessThan(x, y) { - return JSBI.__compareToBigInt(x, y) < 0; - } - - static lessThanOrEqual(x, y) { - return JSBI.__compareToBigInt(x, y) <= 0; - } - - static greaterThan(x, y) { - return JSBI.__compareToBigInt(x, y) > 0; - } - - static greaterThanOrEqual(x, y) { - return JSBI.__compareToBigInt(x, y) >= 0; - } - - static equal(x, y) { - if (x.sign !== y.sign) return false; - if (x.length !== y.length) return false; - for (let i = 0; i < x.length; i++) { - if (x.__digit(i) !== y.__digit(i)) return false; - } - return true; - } - - static bitwiseAnd(x, y) { - if (!x.sign && !y.sign) { - return JSBI.__absoluteAnd(x, y).__trim(); - } else if (x.sign && y.sign) { - const resultLength = Math.max(x.length, y.length) + 1; - // (-x) & (-y) == ~(x-1) & ~(y-1) == ~((x-1) | (y-1)) - // == -(((x-1) | (y-1)) + 1) - let result = JSBI.__absoluteSubOne(x, resultLength); - const y1 = JSBI.__absoluteSubOne(y); - result = JSBI.__absoluteOr(result, y1, result); - return JSBI.__absoluteAddOne(result, true, result).__trim(); - } - // Assume that x is the positive BigInt. - if (x.sign) { - [x, y] = [y, x]; - } - // x & (-y) == x & ~(y-1) == x &~ (y-1) - return JSBI.__absoluteAndNot(x, JSBI.__absoluteSubOne(y)).__trim(); - } - - static bitwiseXor(x, y) { - if (!x.sign && !y.sign) { - return JSBI.__absoluteXor(x, y).__trim(); - } else if (x.sign && y.sign) { - // (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1) - const resultLength = Math.max(x.length, y.length); - const result = JSBI.__absoluteSubOne(x, resultLength); - const y1 = JSBI.__absoluteSubOne(y); - return JSBI.__absoluteXor(result, y1, result).__trim(); - } - const resultLength = Math.max(x.length, y.length) + 1; - // Assume that x is the positive BigInt. - if (x.sign) { - [x, y] = [y, x]; - } - // x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1) - let result = JSBI.__absoluteSubOne(y, resultLength); - result = JSBI.__absoluteXor(result, x, result); - return JSBI.__absoluteAddOne(result, true, result).__trim(); - } - - static bitwiseOr(x, y) { - const resultLength = Math.max(x.length, y.length); - if (!x.sign && !y.sign) { - return JSBI.__absoluteOr(x, y).__trim(); - } else if (x.sign && y.sign) { - // (-x) | (-y) == ~(x-1) | ~(y-1) == ~((x-1) & (y-1)) - // == -(((x-1) & (y-1)) + 1) - let result = JSBI.__absoluteSubOne(x, resultLength); - const y1 = JSBI.__absoluteSubOne(y); - result = JSBI.__absoluteAnd(result, y1, result); - return JSBI.__absoluteAddOne(result, true, result).__trim(); - } - // Assume that x is the positive BigInt. - if (x.sign) { - [x, y] = [y, x]; - } - // x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) ~& x) + 1) - let result = JSBI.__absoluteSubOne(y, resultLength); - result = JSBI.__absoluteAndNot(result, x, result); - return JSBI.__absoluteAddOne(result, true, result).__trim(); - } - - // Operators. - - static ADD(x, y) { - x = JSBI.__toPrimitive(x); - y = JSBI.__toPrimitive(y); - if (typeof x === 'string') { - if (typeof y !== 'string') y = y.toString(); - return x + y; - } - if (typeof y === 'string') { - return x.toString() + y; - } - x = JSBI.__toNumeric(x); - y = JSBI.__toNumeric(y); - if (JSBI.__isBigInt(x) && JSBI.__isBigInt(y)) { - return JSBI.add(x, y); - } - if (typeof x === 'number' && typeof y === 'number') { - return x + y; - } - throw new TypeError( - 'Cannot mix BigInt and other types, use explicit conversions'); - } - - static LT(x, y) { - return JSBI.__compare(x, y, 0); - } - static LE(x, y) { - return JSBI.__compare(x, y, 1); - } - static GT(x, y) { - return JSBI.__compare(x, y, 2); - } - static GE(x, y) { - return JSBI.__compare(x, y, 3); - } - - static EQ(x, y) { - while (true) { - if (JSBI.__isBigInt(x)) { - if (JSBI.__isBigInt(y)) return JSBI.equal(x, y); - return JSBI.EQ(y, x); - } else if (typeof x === 'number') { - if (JSBI.__isBigInt(y)) return JSBI.__equalToNumber(y, x); - if (typeof y !== 'object') return x == y; - y = JSBI.__toPrimitive(y); - } else if (typeof x === 'string') { - if (JSBI.__isBigInt(y)) { - x = JSBI.__fromString(x); - if (x === null) return false; - return JSBI.equal(x, y); - } - if (typeof y !== 'object') return x == y; - y = JSBI.__toPrimitive(y); - } else if (typeof x === 'boolean') { - if (JSBI.__isBigInt(y)) return JSBI.__equalToNumber(y, +x); - if (typeof y !== 'object') return x == y; - y = JSBI.__toPrimitive(y); - } else if (typeof x === 'symbol') { - if (JSBI.__isBigInt(y)) return false; - if (typeof y !== 'object') return x == y; - y = JSBI.__toPrimitive(y); - } else if (typeof x === 'object') { - if (typeof y === 'object' && y.constructor !== JSBI) return x == y; - x = JSBI.__toPrimitive(x); - } else { - return x == y; - } - } - } - - // Helpers. - - static __zero() { - return new JSBI(0, false); - } - - static __oneDigit(value, sign) { - const result = new JSBI(1, sign); - result.__setDigit(0, value); - return result; - } - - __copy() { - const result = new JSBI(this.length, this.sign); - for (let i = 0; i < this.length; i++) { - result[i] = this[i]; - } - return result; - } - - __trim() { - let newLength = this.length; - let last = this[newLength - 1]; - while (last === 0) { - newLength--; - last = this[newLength - 1]; - this.pop(); - } - if (newLength === 0) this.sign = false; - return this; - } - - __initializeDigits() { - for (let i = 0; i < this.length; i++) { - this[i] = 0; - } - } - - static __decideRounding(x, mantissaBitsUnset, digitIndex, currentDigit) { - if (mantissaBitsUnset > 0) return -1; - let topUnconsumedBit; - if (mantissaBitsUnset < 0) { - topUnconsumedBit = -mantissaBitsUnset - 1; - } else { - // {currentDigit} fit the mantissa exactly; look at the next digit. - if (digitIndex === 0) return -1; - digitIndex--; - currentDigit = x.__digit(digitIndex); - topUnconsumedBit = 31; - } - // If the most significant remaining bit is 0, round down. - let mask = 1 << topUnconsumedBit; - if ((currentDigit & mask) === 0) return -1; - // If any other remaining bit is set, round up. - mask -= 1; - if ((currentDigit & mask) !== 0) return 1; - while (digitIndex > 0) { - digitIndex--; - if (x.__digit(digitIndex) !== 0) return 1; - } - return 0; - } - - static __fromDouble(value) { - const sign = value < 0; - JSBI.__kBitConversionDouble[0] = value; - const rawExponent = (JSBI.__kBitConversionInts[1] >>> 20) & 0x7FF; - const exponent = rawExponent - 0x3FF; - const digits = (exponent >>> 5) + 1; - const result = new JSBI(digits, sign); - const kHiddenBit = 0x00100000; - let mantissaHigh = (JSBI.__kBitConversionInts[1] & 0xFFFFF) | kHiddenBit; - let mantissaLow = JSBI.__kBitConversionInts[0]; - const kMantissaHighTopBit = 20; - // 0-indexed position of most significant bit in most significant digit. - const msdTopBit = exponent & 31; - // Number of unused bits in the mantissa. We'll keep them shifted to the - // left (i.e. most significant part). - let remainingMantissaBits = 0; - // Next digit under construction. - let digit; - // First, build the MSD by shifting the mantissa appropriately. - if (msdTopBit < kMantissaHighTopBit) { - const shift = kMantissaHighTopBit - msdTopBit; - remainingMantissaBits = shift + 32; - digit = mantissaHigh >>> shift; - mantissaHigh = (mantissaHigh << (32 - shift)) | - (mantissaLow >>> shift); - mantissaLow = mantissaLow << (32 - shift); - } else if (msdTopBit === kMantissaHighTopBit) { - remainingMantissaBits = 32; - digit = mantissaHigh; - mantissaHigh = mantissaLow; - } else { - const shift = msdTopBit - kMantissaHighTopBit; - remainingMantissaBits = 32 - shift; - digit = (mantissaHigh << shift) | (mantissaLow >>> (32 - shift)); - mantissaHigh = mantissaLow << shift; - } - result.__setDigit(digits - 1, digit); - // Then fill in the rest of the digits. - for (let digitIndex = digits - 2; digitIndex >= 0; digitIndex--) { - if (remainingMantissaBits > 0) { - remainingMantissaBits -= 32; - digit = mantissaHigh; - mantissaHigh = mantissaLow; - } else { - digit = 0; - } - result.__setDigit(digitIndex, digit); - } - return result.__trim(); - } - - static __isWhitespace(c) { - if (c <= 0x0D && c >= 0x09) return true; - if (c <= 0x9F) return c === 0x20; - if (c <= 0x01FFFF) { - return c === 0xA0 || c === 0x1680; - } - if (c <= 0x02FFFF) { - c &= 0x01FFFF; - return c <= 0x0A || c === 0x28 || c === 0x29 || c === 0x2F || - c === 0x5F || c === 0x1000; - } - return c === 0xFEFF; - } - - static __fromString(string, radix = 0) { - let sign = 0; - let leadingZero = false; - const length = string.length; - let cursor = 0; - if (cursor === length) return JSBI.__zero(); - let current = string.charCodeAt(cursor); - // Skip whitespace. - while (JSBI.__isWhitespace(current)) { - if (++cursor === length) return JSBI.__zero(); - current = string.charCodeAt(cursor); - } - - // Detect radix. - if (current === 0x2B) { // '+' - if (++cursor === length) return null; - current = string.charCodeAt(cursor); - sign = 1; - } else if (current === 0x2D) { // '-' - if (++cursor === length) return null; - current = string.charCodeAt(cursor); - sign = -1; - } - - if (radix === 0) { - radix = 10; - if (current === 0x30) { // '0' - if (++cursor === length) return JSBI.__zero(); - current = string.charCodeAt(cursor); - if (current === 0x58 || current === 0x78) { // 'X' or 'x' - radix = 16; - if (++cursor === length) return null; - current = string.charCodeAt(cursor); - } else if (current === 0x4F || current === 0x6F) { // 'O' or 'o' - radix = 8; - if (++cursor === length) return null; - current = string.charCodeAt(cursor); - } else if (current === 0x42 || current === 0x62) { // 'B' or 'b' - radix = 2; - if (++cursor === length) return null; - current = string.charCodeAt(cursor); - } else { - leadingZero = true; - } - } - } else if (radix === 16) { - if (current === 0x30) { // '0' - // Allow "0x" prefix. - if (++cursor === length) return JSBI.__zero(); - current = string.charCodeAt(cursor); - if (current === 0x58 || current === 0x78) { // 'X' or 'x' - if (++cursor === length) return null; - current = string.charCodeAt(cursor); - } else { - leadingZero = true; - } - } - } - // Skip leading zeros. - while (current === 0x30) { - leadingZero = true; - if (++cursor === length) return JSBI.__zero(); - current = string.charCodeAt(cursor); - } - - // Allocate result. - const chars = length - cursor; - let bitsPerChar = JSBI.__kMaxBitsPerChar[radix]; - let roundup = JSBI.__kBitsPerCharTableMultiplier - 1; - if (chars > (1 << 30) / bitsPerChar) return null; - const bitsMin = - (bitsPerChar * chars + roundup) >>> JSBI.__kBitsPerCharTableShift; - const resultLength = (bitsMin + 31) >>> 5; - const result = new JSBI(resultLength, false); - - // Parse. - const limDigit = radix < 10 ? radix : 10; - const limAlpha = radix > 10 ? radix - 10 : 0; - - if ((radix & (radix - 1)) === 0) { - // Power-of-two radix. - bitsPerChar >>= JSBI.__kBitsPerCharTableShift; - const parts = []; - const partsBits = []; - let done = false; - do { - let part = 0; - let bits = 0; - while (true) { - let d; - if (((current - 48) >>> 0) < limDigit) { - d = current - 48; - } else if ((((current | 32) - 97) >>> 0) < limAlpha) { - d = (current | 32) - 87; - } else { - done = true; - break; - } - bits += bitsPerChar; - part = (part << bitsPerChar) | d; - if (++cursor === length) { - done = true; - break; - } - current = string.charCodeAt(cursor); - if (bits + bitsPerChar > 32) break; - } - parts.push(part); - partsBits.push(bits); - } while (!done); - JSBI.__fillFromParts(result, parts, partsBits); - } else { - result.__initializeDigits(); - let done = false; - let charsSoFar = 0; - do { - let part = 0; - let multiplier = 1; - while (true) { - let d; - if (((current - 48) >>> 0) < limDigit) { - d = current - 48; - } else if ((((current | 32) - 97) >>> 0) < limAlpha) { - d = (current | 32) - 87; - } else { - done = true; - break; - } - - const m = multiplier * radix; - if (m > 0xFFFFFFFF) break; - multiplier = m; - part = part * radix + d; - charsSoFar++; - if (++cursor === length) { - done = true; - break; - } - current = string.charCodeAt(cursor); - } - roundup = JSBI.__kBitsPerCharTableMultiplier * 32 - 1; - const digitsSoFar = (bitsPerChar * charsSoFar + roundup) >>> - (JSBI.__kBitsPerCharTableShift + 5); - result.__inplaceMultiplyAdd(multiplier, part, digitsSoFar); - } while (!done); - } - - while (cursor !== length) { - if (!JSBI.__isWhitespace(current)) return null; - current = string.charCodeAt(cursor++); - } - - // Get result. - if (sign !== 0 && radix !== 10) return null; - result.sign = (sign === -1); - return result.__trim(); - } - - static __fillFromParts(result, parts, partsBits) { - let digitIndex = 0; - let digit = 0; - let bitsInDigit = 0; - for (let i = parts.length - 1; i >= 0; i--) { - const part = parts[i]; - const partBits = partsBits[i]; - digit |= (part << bitsInDigit); - bitsInDigit += partBits; - if (bitsInDigit === 32) { - result.__setDigit(digitIndex++, digit); - bitsInDigit = 0; - digit = 0; - } else if (bitsInDigit > 32) { - result.__setDigit(digitIndex++, digit); - bitsInDigit -= 32; - digit = part >>> (partBits - bitsInDigit); - } - } - if (digit !== 0) { - if (digitIndex >= result.length) throw new Error('implementation bug'); - result.__setDigit(digitIndex++, digit); - } - for (; digitIndex < result.length; digitIndex++) { - result.__setDigit(digitIndex, 0); - } - } - - static __toStringBasePowerOfTwo(x, radix) { - const length = x.length; - let bits = radix - 1; - bits = ((bits >>> 1) & 0x55) + (bits & 0x55); - bits = ((bits >>> 2) & 0x33) + (bits & 0x33); - bits = ((bits >>> 4) & 0x0F) + (bits & 0x0F); - const bitsPerChar = bits; - const charMask = radix - 1; - const msd = x.__digit(length - 1); - const msdLeadingZeros = Math.clz32(msd); - const bitLength = length * 32 - msdLeadingZeros; - let charsRequired = - ((bitLength + bitsPerChar - 1) / bitsPerChar) | 0; - if (x.sign) charsRequired++; - if (charsRequired > (1 << 28)) throw new Error('string too long'); - const result = new Array(charsRequired); - let pos = charsRequired - 1; - let digit = 0; - let availableBits = 0; - for (let i = 0; i < length - 1; i++) { - const newDigit = x.__digit(i); - const current = (digit | (newDigit << availableBits)) & charMask; - result[pos--] = JSBI.__kConversionChars[current]; - const consumedBits = bitsPerChar - availableBits; - digit = newDigit >>> consumedBits; - availableBits = 32 - consumedBits; - while (availableBits >= bitsPerChar) { - result[pos--] = JSBI.__kConversionChars[digit & charMask]; - digit >>>= bitsPerChar; - availableBits -= bitsPerChar; - } - } - const current = (digit | (msd << availableBits)) & charMask; - result[pos--] = JSBI.__kConversionChars[current]; - digit = msd >>> (bitsPerChar - availableBits); - while (digit !== 0) { - result[pos--] = JSBI.__kConversionChars[digit & charMask]; - digit >>>= bitsPerChar; - } - if (x.sign) result[pos--] = '-'; - if (pos !== -1) throw new Error('implementation bug'); - return result.join(''); - } - - static __toStringGeneric(x, radix, isRecursiveCall) { - const length = x.length; - if (length === 0) return ''; - if (length === 1) { - let result = x.__unsignedDigit(0).toString(radix); - if (isRecursiveCall === false && x.sign) { - result = '-' + result; - } - return result; - } - const bitLength = length * 32 - Math.clz32(x.__digit(length - 1)); - const maxBitsPerChar = JSBI.__kMaxBitsPerChar[radix]; - const minBitsPerChar = maxBitsPerChar - 1; - let charsRequired = bitLength * JSBI.__kBitsPerCharTableMultiplier; - charsRequired += minBitsPerChar - 1; - charsRequired = (charsRequired / minBitsPerChar) | 0; - const secondHalfChars = (charsRequired + 1) >> 1; - // Divide-and-conquer: split by a power of {radix} that's approximately - // the square root of {x}, then recurse. - const conqueror = JSBI.exponentiate(JSBI.__oneDigit(radix, false), - JSBI.__oneDigit(secondHalfChars, false)); - let quotient; - let secondHalf; - const divisor = conqueror.__unsignedDigit(0); - if (conqueror.length === 1 && divisor <= 0xFFFF) { - quotient = new JSBI(x.length, false); - quotient.__initializeDigits(); - let remainder = 0; - for (let i = x.length * 2 - 1; i >= 0; i--) { - const input = (remainder << 16) | x.__halfDigit(i); - quotient.__setHalfDigit(i, (input / divisor) | 0); - remainder = (input % divisor) | 0; - } - secondHalf = remainder.toString(radix); - } else { - const divisionResult = JSBI.__absoluteDivLarge(x, conqueror, true, true); - quotient = divisionResult.quotient; - const remainder = divisionResult.remainder.__trim(); - secondHalf = JSBI.__toStringGeneric(remainder, radix, true); - } - quotient.__trim(); - let firstHalf = JSBI.__toStringGeneric(quotient, radix, true); - while (secondHalf.length < secondHalfChars) { - secondHalf = '0' + secondHalf; - } - if (isRecursiveCall === false && x.sign) { - firstHalf = '-' + firstHalf; - } - return firstHalf + secondHalf; - } - - static __unequalSign(leftNegative) { - return leftNegative ? -1 : 1; - } - static __absoluteGreater(bothNegative) { - return bothNegative ? -1 : 1; - } - static __absoluteLess(bothNegative) { - return bothNegative ? 1 : -1; - } - - static __compareToBigInt(x, y) { - const xSign = x.sign; - if (xSign !== y.sign) return JSBI.__unequalSign(xSign); - const result = JSBI.__absoluteCompare(x, y); - if (result > 0) return JSBI.__absoluteGreater(xSign); - if (result < 0) return JSBI.__absoluteLess(xSign); - return 0; - } - - static __compareToNumber(x, y) { - if (y | 0 === 0) { - const xSign = x.sign; - const ySign = (y < 0); - if (xSign !== ySign) return JSBI.__unequalSign(xSign); - if (x.length === 0) { - if (ySign) throw new Error('implementation bug'); - return y === 0 ? 0 : -1; - } - // Any multi-digit BigInt is bigger than an int32. - if (x.length > 1) return JSBI.__absoluteGreater(xSign); - const yAbs = Math.abs(y); - const xDigit = x.__unsignedDigit(0); - if (xDigit > yAbs) return JSBI.__absoluteGreater(xSign); - if (xDigit < yAbs) return JSBI.__absoluteLess(xSign); - return 0; - } - return JSBI.__compareToDouble(x, y); - } - - static __compareToDouble(x, y) { - if (y !== y) return y; // NaN. - if (y === Infinity) return -1; - if (y === -Infinity) return 1; - const xSign = x.sign; - const ySign = (y < 0); - if (xSign !== ySign) return JSBI.__unequalSign(xSign); - if (y === 0) { - throw new Error('implementation bug: should be handled elsewhere'); - } - if (x.length === 0) return -1; - JSBI.__kBitConversionDouble[0] = y; - const rawExponent = (JSBI.__kBitConversionInts[1] >>> 20) & 0x7FF; - if (rawExponent === 0x7FF) { - throw new Error('implementation bug: handled elsewhere'); - } - const exponent = rawExponent - 0x3FF; - if (exponent < 0) { - // The absolute value of y is less than 1. Only 0n has an absolute - // value smaller than that, but we've already covered that case. - return JSBI.__absoluteGreater(xSign); - } - const xLength = x.length; - let xMsd = x.__digit(xLength - 1); - const msdLeadingZeros = Math.clz32(xMsd); - const xBitLength = xLength * 32 - msdLeadingZeros; - const yBitLength = exponent + 1; - if (xBitLength < yBitLength) return JSBI.__absoluteLess(xSign); - if (xBitLength > yBitLength) return JSBI.__absoluteGreater(xSign); - // Same sign, same bit length. Shift mantissa to align with x and compare - // bit for bit. - const kHiddenBit = 0x00100000; - let mantissaHigh = (JSBI.__kBitConversionInts[1] & 0xFFFFF) | kHiddenBit; - let mantissaLow = JSBI.__kBitConversionInts[0]; - const kMantissaHighTopBit = 20; - const msdTopBit = 31 - msdLeadingZeros; - if (msdTopBit !== ((xBitLength - 1) % 31)) { - throw new Error('implementation bug'); - } - let compareMantissa; // Shifted chunk of mantissa. - let remainingMantissaBits = 0; - // First, compare most significant digit against beginning of mantissa. - if (msdTopBit < kMantissaHighTopBit) { - const shift = kMantissaHighTopBit - msdTopBit; - remainingMantissaBits = shift + 32; - compareMantissa = mantissaHigh >>> shift; - mantissaHigh = (mantissaHigh << (32 - shift)) | (mantissaLow >>> shift); - mantissaLow = mantissaLow << (32 - shift); - } else if (msdTopBit === kMantissaHighTopBit) { - remainingMantissaBits = 32; - compareMantissa = mantissaHigh; - mantissaHigh = mantissaLow; - } else { - const shift = msdTopBit - kMantissaHighTopBit; - remainingMantissaBits = 32 - shift; - compareMantissa = - (mantissaHigh << shift) | (mantissaLow >>> (32 - shift)); - mantissaHigh = mantissaLow << shift; - } - xMsd = xMsd >>> 0; - compareMantissa = compareMantissa >>> 0; - if (xMsd > compareMantissa) return JSBI.__absoluteGreater(xSign); - if (xMsd < compareMantissa) return JSBI.__absoluteLess(xSign); - // Then, compare additional digits against remaining mantissa bits. - for (let digitIndex = xLength - 2; digitIndex >= 0; digitIndex--) { - if (remainingMantissaBits > 0) { - remainingMantissaBits -= 32; - compareMantissa = mantissaHigh >>> 0; - mantissaHigh = mantissaLow; - mantissaLow = 0; - } else { - compareMantissa = 0; - } - const digit = x.__unsignedDigit(digitIndex); - if (digit > compareMantissa) return JSBI.__absoluteGreater(xSign); - if (digit < compareMantissa) return JSBI.__absoluteLess(xSign); - } - // Integer parts are equal; check whether {y} has a fractional part. - if (mantissaHigh !== 0 || mantissaLow !== 0) { - if (remainingMantissaBits === 0) throw new Error('implementation bug'); - return JSBI.__absoluteLess(xSign); - } - return 0; - } - - static __equalToNumber(x, y) { - if (y | 0 === y) { - if (y === 0) return x.length === 0; - // Any multi-digit BigInt is bigger than an int32. - return (x.length === 1) && (x.sign === (y < 0)) && - (x.__unsignedDigit(0) === Math.abs(y)); - } - return JSBI.__compareToDouble(x, y) === 0; - } - - // Comparison operations, chosen such that "op ^ 2" reverses direction: - // 0 - lessThan - // 1 - lessThanOrEqual - // 2 - greaterThan - // 3 - greaterThanOrEqual - static __comparisonResultToBool(result, op) { - switch (op) { - case 0: return result < 0; - case 1: return result <= 0; - case 2: return result > 0; - case 3: return result >= 0; - } - throw new Error('unreachable'); - } - - static __compare(x, y, op) { - x = JSBI.__toPrimitive(x); - y = JSBI.__toPrimitive(y); - if (typeof x === 'string' && typeof y === 'string') { - switch (op) { - case 0: return x < y; - case 1: return x <= y; - case 2: return x > y; - case 3: return x >= y; - } - } - if (JSBI.__isBigInt(x) && typeof y === 'string') { - y = JSBI.__fromString(y); - if (y === null) return false; - return JSBI.__comparisonResultToBool(JSBI.__compareToBigInt(x, y), op); - } - if (typeof x === 'string' && JSBI.__isBigInt(y)) { - x = JSBI.__fromString(x); - if (x === null) return false; - return JSBI.__comparisonResultToBool(JSBI.__compareToBigInt(x, y), op); - } - x = JSBI.__toNumeric(x); - y = JSBI.__toNumeric(y); - if (JSBI.__isBigInt(x)) { - if (JSBI.__isBigInt(y)) { - return JSBI.__comparisonResultToBool(JSBI.__compareToBigInt(x, y), op); - } - if (typeof y !== 'number') throw new Error('implementation bug'); - return JSBI.__comparisonResultToBool(JSBI.__compareToNumber(x, y), op); - } - if (typeof x !== 'number') throw new Error('implementation bug'); - if (JSBI.__isBigInt(y)) { - // Note that "op ^ 2" reverses the op's direction. - return JSBI.__comparisonResultToBool(JSBI.__compareToNumber(y, x), - op ^ 2); - } - if (typeof y !== 'number') throw new Error('implementation bug'); - switch (op) { - case 0: return x < y; - case 1: return x <= y; - case 2: return x > y; - case 3: return x >= y; - } - } - - __clzmsd() { - return Math.clz32(this[this.length - 1]); - } - - static __absoluteAdd(x, y, resultSign) { - if (x.length < y.length) return JSBI.__absoluteAdd(y, x, resultSign); - if (x.length === 0) return x; - if (y.length === 0) return x.sign === resultSign ? x : JSBI.unaryMinus(x); - let resultLength = x.length; - if (x.__clzmsd() === 0 || (y.length === x.length && y.__clzmsd() === 0)) { - resultLength++; - } - const result = new JSBI(resultLength, resultSign); - let carry = 0; - let i = 0; - for (; i < y.length; i++) { - const yDigit = y.__digit(i); - const xDigit = x.__digit(i); - const rLow = (xDigit & 0xFFFF) + (yDigit & 0xFFFF) + carry; - const rHigh = (xDigit >>> 16) + (yDigit >>> 16) + (rLow >>> 16); - carry = rHigh >>> 16; - result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); - } - for (; i < x.length; i++) { - const xDigit = x.__digit(i); - const rLow = (xDigit & 0xFFFF) + carry; - const rHigh = (xDigit >>> 16) + (rLow >>> 16); - carry = rHigh >>> 16; - result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); - } - if (i < result.length) { - result.__setDigit(i, carry); - } - return result.__trim(); - } - - static __absoluteSub(x, y, resultSign) { - if (x.length === 0) return x; - if (y.length === 0) return x.sign === resultSign ? x : JSBI.unaryMinus(x); - const result = new JSBI(x.length, resultSign); - let borrow = 0; - let i = 0; - for (; i < y.length; i++) { - const xDigit = x.__digit(i); - const yDigit = y.__digit(i); - const rLow = (xDigit & 0xFFFF) - (yDigit & 0xFFFF) - borrow; - borrow = (rLow >>> 16) & 1; - const rHigh = (xDigit >>> 16) - (yDigit >>> 16) - borrow; - borrow = (rHigh >>> 16) & 1; - result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); - } - for (; i < x.length; i++) { - const xDigit = x.__digit(i); - const rLow = (xDigit & 0xFFFF) - borrow; - borrow = (rLow >>> 16) & 1; - const rHigh = (xDigit >>> 16) - borrow; - borrow = (rHigh >>> 16) & 1; - result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); - } - return result.__trim(); - } - - static __absoluteAddOne(x, sign, result = null) { - const inputLength = x.length; - if (result === null) { - result = new JSBI(inputLength, sign); - } else { - result.sign = sign; - } - let carry = true; - for (let i = 0; i < inputLength; i++) { - let digit = x.__digit(i); - const newCarry = digit === (0xFFFFFFFF | 0); - if (carry) digit = (digit + 1) | 0; - carry = newCarry; - result.__setDigit(i, digit); - } - if (carry) { - result.__setDigitGrow(inputLength, 1); - } - return result; - } - - static __absoluteSubOne(x, resultLength) { - const length = x.length; - resultLength = resultLength || length; - const result = new JSBI(resultLength, false); - let borrow = true; - for (let i = 0; i < length; i++) { - let digit = x.__digit(i); - const newBorrow = digit === 0; - if (borrow) digit = (digit - 1) | 0; - borrow = newBorrow; - result.__setDigit(i, digit); - } - for (let i = length; i < resultLength; i++) { - result.__setDigit(i, 0); - } - return result; - } - - static __absoluteAnd(x, y, result = null) { - let xLength = x.length; - let yLength = y.length; - let numPairs = yLength; - if (xLength < yLength) { - numPairs = xLength; - const tmp = x; - const tmpLength = xLength; - x = y; - xLength = yLength; - y = tmp; - yLength = tmpLength; - } - let resultLength = numPairs; - if (result === null) { - result = new JSBI(resultLength, false); - } else { - resultLength = result.length; - } - let i = 0; - for (; i < numPairs; i++) { - result.__setDigit(i, x.__digit(i) & y.__digit(i)); - } - for (; i < resultLength; i++) { - result.__setDigit(i, 0); - } - return result; - } - - static __absoluteAndNot(x, y, result = null) { - const xLength = x.length; - const yLength = y.length; - let numPairs = yLength; - if (xLength < yLength) { - numPairs = xLength; - } - let resultLength = xLength; - if (result === null) { - result = new JSBI(resultLength, false); - } else { - resultLength = result.length; - } - let i = 0; - for (; i < numPairs; i++) { - result.__setDigit(i, x.__digit(i) & ~y.__digit(i)); - } - for (; i < xLength; i++) { - result.__setDigit(i, x.__digit(i)); - } - for (; i < resultLength; i++) { - result.__setDigit(i, 0); - } - return result; - } - - static __absoluteOr(x, y, result = null) { - let xLength = x.length; - let yLength = y.length; - let numPairs = yLength; - if (xLength < yLength) { - numPairs = xLength; - const tmp = x; - const tmpLength = xLength; - x = y; - xLength = yLength; - y = tmp; - yLength = tmpLength; - } - let resultLength = xLength; - if (result === null) { - result = new JSBI(resultLength, false); - } else { - resultLength = result.length; - } - let i = 0; - for (; i < numPairs; i++) { - result.__setDigit(i, x.__digit(i) | y.__digit(i)); - } - for (; i < xLength; i++) { - result.__setDigit(i, x.__digit(i)); - } - for (; i < resultLength; i++) { - result.__setDigit(i, 0); - } - return result; - } - - static __absoluteXor(x, y, result = null) { - let xLength = x.length; - let yLength = y.length; - let numPairs = yLength; - if (xLength < yLength) { - numPairs = xLength; - const tmp = x; - const tmpLength = xLength; - x = y; - xLength = yLength; - y = tmp; - yLength = tmpLength; - } - let resultLength = xLength; - if (result === null) { - result = new JSBI(resultLength, false); - } else { - resultLength = result.length; - } - let i = 0; - for (; i < numPairs; i++) { - result.__setDigit(i, x.__digit(i) ^ y.__digit(i)); - } - for (; i < xLength; i++) { - result.__setDigit(i, x.__digit(i)); - } - for (; i < resultLength; i++) { - result.__setDigit(i, 0); - } - return result; - } - - static __absoluteCompare(x, y) { - const diff = x.length - y.length; - if (diff !== 0) return diff; - let i = x.length - 1; - while (i >= 0 && x.__digit(i) === y.__digit(i)) i--; - if (i < 0) return 0; - return x.__unsignedDigit(i) > y.__unsignedDigit(i) ? 1 : -1; - } - - static __multiplyAccumulate(multiplicand, multiplier, accumulator, - accumulatorIndex) { - if (multiplier === 0) return; - const m2Low = multiplier & 0xFFFF; - const m2High = multiplier >>> 16; - let carry = 0; - let highLower = 0; - let highHigher = 0; - for (let i = 0; i < multiplicand.length; i++, accumulatorIndex++) { - let acc = accumulator.__digit(accumulatorIndex); - let accLow = acc & 0xFFFF; - let accHigh = acc >>> 16; - const m1 = multiplicand.__digit(i); - const m1Low = m1 & 0xFFFF; - const m1High = m1 >>> 16; - const rLow = Math.imul(m1Low, m2Low); - const rMid1 = Math.imul(m1Low, m2High); - const rMid2 = Math.imul(m1High, m2Low); - const rHigh = Math.imul(m1High, m2High); - accLow += highLower + (rLow & 0xFFFF); - accHigh += highHigher + carry + (accLow >>> 16) + (rLow >>> 16) + - (rMid1 & 0xFFFF) + (rMid2 & 0xFFFF); - carry = accHigh >>> 16; - highLower = (rMid1 >>> 16) + (rMid2 >>> 16) + (rHigh & 0xFFFF) + carry; - carry = highLower >>> 16; - highLower &= 0xFFFF; - highHigher = rHigh >>> 16; - acc = (accLow & 0xFFFF) | (accHigh << 16); - accumulator.__setDigit(accumulatorIndex, acc); - } - for (; carry !== 0 || highLower !== 0 || highHigher !== 0; - accumulatorIndex++) { - let acc = accumulator.__digit(accumulatorIndex); - const accLow = (acc & 0xFFFF) + highLower; - const accHigh = (acc >>> 16) + (accLow >>> 16) + highHigher + carry; - highLower = 0; - highHigher = 0; - carry = accHigh >>> 16; - acc = (accLow & 0xFFFF) | (accHigh << 16); - accumulator.__setDigit(accumulatorIndex, acc); - } - } - - static __internalMultiplyAdd(source, factor, summand, n, result) { - let carry = summand; - let high = 0; - for (let i = 0; i < n; i++) { - const digit = source.__digit(i); - const rx = Math.imul(digit & 0xFFFF, factor); - const r0 = (rx & 0xFFFF) + high + carry; - carry = r0 >>> 16; - const ry = Math.imul(digit >>> 16, factor); - const r16 = (ry & 0xFFFF) + (rx >>> 16) + carry; - carry = r16 >>> 16; - high = ry >>> 16; - result.__setDigit(i, (r16 << 16) | (r0 & 0xFFFF)); - } - if (result.length > n) { - result.__setDigit(n++, carry + high); - while (n < result.length) { - result.__setDigit(n++, 0); - } - } else { - if (carry + high !== 0) throw new Error('implementation bug'); - } - } - - __inplaceMultiplyAdd(multiplier, summand, length) { - if (length > this.length) length = this.length; - const mLow = multiplier & 0xFFFF; - const mHigh = multiplier >>> 16; - let carry = 0; - let highLower = summand & 0xFFFF; - let highHigher = summand >>> 16; - for (let i = 0; i < length; i++) { - const d = this.__digit(i); - const dLow = d & 0xFFFF; - const dHigh = d >>> 16; - const pLow = Math.imul(dLow, mLow); - const pMid1 = Math.imul(dLow, mHigh); - const pMid2 = Math.imul(dHigh, mLow); - const pHigh = Math.imul(dHigh, mHigh); - const rLow = highLower + (pLow & 0xFFFF); - const rHigh = highHigher + carry + (rLow >>> 16) + (pLow >>> 16) + - (pMid1 & 0xFFFF) + (pMid2 & 0xFFFF); - highLower = (pMid1 >>> 16) + (pMid2 >>> 16) + (pHigh & 0xFFFF) + - (rHigh >>> 16); - carry = highLower >>> 16; - highLower &= 0xFFFF; - highHigher = pHigh >>> 16; - const result = (rLow & 0xFFFF) | (rHigh << 16); - this.__setDigit(i, result); - } - if (carry !== 0 || highLower !== 0 || highHigher !== 0) { - throw new Error('implementation bug'); - } - } - - static __absoluteDivSmall(x, divisor, quotient) { - if (quotient === null) quotient = new JSBI(x.length, false); - let remainder = 0; - for (let i = x.length * 2 - 1; i >= 0; i -= 2) { - let input = ((remainder << 16) | x.__halfDigit(i)) >>> 0; - const upperHalf = (input / divisor) | 0; - remainder = (input % divisor) | 0; - input = ((remainder << 16) | x.__halfDigit(i - 1)) >>> 0; - const lowerHalf = (input / divisor) | 0; - remainder = (input % divisor) | 0; - quotient.__setDigit(i >>> 1, (upperHalf << 16) | lowerHalf); - } - return quotient; - } - - static __absoluteModSmall(x, divisor) { - let remainder = 0; - for (let i = x.length * 2 - 1; i >= 0; i--) { - const input = ((remainder << 16) | x.__halfDigit(i)) >>> 0; - remainder = (input % divisor) | 0; - } - return remainder; - } - - static __absoluteDivLarge(dividend, divisor, wantQuotient, wantRemainder) { - const n = divisor.__halfDigitLength(); - const n2 = divisor.length; - const m = dividend.__halfDigitLength() - n; - let q = null; - if (wantQuotient) { - q = new JSBI((m + 2) >>> 1, false); - q.__initializeDigits(); - } - const qhatv = new JSBI((n + 2) >>> 1, false); - qhatv.__initializeDigits(); - // D1. - const shift = JSBI.__clz16(divisor.__halfDigit(n - 1)); - if (shift > 0) { - divisor = JSBI.__specialLeftShift(divisor, shift, 0 /* add no digits*/); - } - const u = JSBI.__specialLeftShift(dividend, shift, 1 /* add one digit */); - // D2. - const vn1 = divisor.__halfDigit(n - 1); - let halfDigitBuffer = 0; - for (let j = m; j >= 0; j--) { - // D3. - let qhat = 0xFFFF; - const ujn = u.__halfDigit(j + n); - if (ujn !== vn1) { - const input = ((ujn << 16) | u.__halfDigit(j + n - 1)) >>> 0; - qhat = (input / vn1) | 0; - let rhat = (input % vn1) | 0; - const vn2 = divisor.__halfDigit(n - 2); - const ujn2 = u.__halfDigit(j + n - 2); - while ((Math.imul(qhat, vn2) >>> 0) > (((rhat << 16) | ujn2) >>> 0)) { - qhat--; - rhat += vn1; - if (rhat > 0xFFFF) break; - } - } - // D4. - JSBI.__internalMultiplyAdd(divisor, qhat, 0, n2, qhatv); - let c = u.__inplaceSub(qhatv, j, n + 1); - if (c !== 0) { - c = u.__inplaceAdd(divisor, j, n); - u.__setHalfDigit(j + n, u.__halfDigit(j + n) + c); - qhat--; - } - if (wantQuotient) { - if (j & 1) { - halfDigitBuffer = qhat << 16; - } else { - q.__setDigit(j >>> 1, halfDigitBuffer | qhat); - } - } - } - if (wantRemainder) { - u.__inplaceRightShift(shift); - if (wantQuotient) { - return {quotient: q, remainder: u}; - } - return u; - } - if (wantQuotient) return q; - } - - static __clz16(value) { - return Math.clz32(value) - 16; - } - - // TODO: work on full digits, like __inplaceSub? - __inplaceAdd(summand, startIndex, halfDigits) { - let carry = 0; - for (let i = 0; i < halfDigits; i++) { - const sum = this.__halfDigit(startIndex + i) + - summand.__halfDigit(i) + - carry; - carry = sum >>> 16; - this.__setHalfDigit(startIndex + i, sum); - } - return carry; - } - - __inplaceSub(subtrahend, startIndex, halfDigits) { - const fullSteps = (halfDigits - 1) >>> 1; - let borrow = 0; - if (startIndex & 1) { - // this: [..][..][..] - // subtr.: [..][..] - startIndex >>= 1; - let current = this.__digit(startIndex); - let r0 = current & 0xFFFF; - let i = 0; - for (; i < fullSteps; i++) { - const sub = subtrahend.__digit(i); - const r16 = (current >>> 16) - (sub & 0xFFFF) - borrow; - borrow = (r16 >>> 16) & 1; - this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); - current = this.__digit(startIndex + i + 1); - r0 = (current & 0xFFFF) - (sub >>> 16) - borrow; - borrow = (r0 >>> 16) & 1; - } - // Unrolling the last iteration gives a 5% performance benefit! - const sub = subtrahend.__digit(i); - const r16 = (current >>> 16) - (sub & 0xFFFF) - borrow; - borrow = (r16 >>> 16) & 1; - this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); - const subTop = sub >>> 16; - if (startIndex + i + 1 >= this.length) { - throw new RangeError('out of bounds'); - } - if ((halfDigits & 1) === 0) { - current = this.__digit(startIndex + i + 1); - r0 = (current & 0xFFFF) - subTop - borrow; - borrow = (r0 >>> 16) & 1; - this.__setDigit(startIndex + subtrahend.length, - (current & 0xFFFF0000) | (r0 & 0xFFFF)); - } - } else { - startIndex >>= 1; - let i = 0; - for (; i < subtrahend.length - 1; i++) { - const current = this.__digit(startIndex + i); - const sub = subtrahend.__digit(i); - const r0 = (current & 0xFFFF) - (sub & 0xFFFF) - borrow; - borrow = (r0 >>> 16) & 1; - const r16 = (current >>> 16) - (sub >>> 16) - borrow; - borrow = (r16 >>> 16) & 1; - this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); - } - const current = this.__digit(startIndex + i); - const sub = subtrahend.__digit(i); - const r0 = (current & 0xFFFF) - (sub & 0xFFFF) - borrow; - borrow = (r0 >>> 16) & 1; - let r16 = 0; - if ((halfDigits & 1) === 0) { - r16 = (current >>> 16) - (sub >>> 16) - borrow; - borrow = (r16 >>> 16) & 1; - } - this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); - } - return borrow; - } - - __inplaceRightShift(shift) { - if (shift === 0) return; - let carry = this.__digit(0) >>> shift; - const last = this.length - 1; - for (let i = 0; i < last; i++) { - const d = this.__digit(i + 1); - this.__setDigit(i, (d << (32 - shift)) | carry); - carry = d >>> shift; - } - this.__setDigit(last, carry); - } - - static __specialLeftShift(x, shift, addDigit) { - const n = x.length; - const resultLength = n + addDigit; - const result = new JSBI(resultLength, false); - if (shift === 0) { - for (let i = 0; i < n; i++) result.__setDigit(i, x.__digit(i)); - if (addDigit > 0) result.__setDigit(n, 0); - return result; - } - let carry = 0; - for (let i = 0; i < n; i++) { - const d = x.__digit(i); - result.__setDigit(i, (d << shift) | carry); - carry = d >>> (32 - shift); - } - if (addDigit > 0) { - result.__setDigit(n, carry); - } - return result; - } - - static __leftShiftByAbsolute(x, y) { - const shift = JSBI.__toShiftAmount(y); - if (shift < 0) throw new RangeError('BigInt too big'); - const digitShift = shift >>> 5; - const bitsShift = shift & 31; - const length = x.length; - const grow = bitsShift !== 0 && - (x.__digit(length - 1) >>> (32 - bitsShift)) !== 0; - const resultLength = length + digitShift + (grow ? 1 : 0); - const result = new JSBI(resultLength, x.sign); - if (bitsShift === 0) { - let i = 0; - for (; i < digitShift; i++) result.__setDigit(i, 0); - for (; i < resultLength; i++) { - result.__setDigit(i, x.__digit(i - digitShift)); - } - } else { - let carry = 0; - for (let i = 0; i < digitShift; i++) result.__setDigit(i, 0); - for (let i = 0; i < length; i++) { - const d = x.__digit(i); - result.__setDigit(i + digitShift, (d << bitsShift) | carry); - carry = d >>> (32 - bitsShift); - } - if (grow) { - result.__setDigit(length + digitShift, carry); - } else { - if (carry !== 0) throw new Error('implementation bug'); - } - } - return result.__trim(); - } - - static __rightShiftByAbsolute(x, y) { - const length = x.length; - const sign = x.sign; - const shift = JSBI.__toShiftAmount(y); - if (shift < 0) return JSBI.__rightShiftByMaximum(sign); - const digitShift = shift >>> 5; - const bitsShift = shift & 31; - let resultLength = length - digitShift; - if (resultLength <= 0) return JSBI.__rightShiftByMaximum(sign); - // For negative numbers, round down if any bit was shifted out (so that - // e.g. -5n >> 1n == -3n and not -2n). Check now whether this will happen - // and whether itc an cause overflow into a new digit. If we allocate the - // result large enough up front, it avoids having to do grow it later. - let mustRoundDown = false; - if (sign) { - const mask = (1 << bitsShift) - 1; - if ((x.__digit(digitShift) & mask) !== 0) { - mustRoundDown = true; - } else { - for (let i = 0; i < digitShift; i++) { - if (x.__digit(i) !== 0) { - mustRoundDown = true; - break; - } - } - } - } - // If bitsShift is non-zero, it frees up bits, preventing overflow. - if (mustRoundDown && bitsShift === 0) { - // Overflow cannot happen if the most significant digit has unset bits. - const msd = x.__digit(length - 1); - const roundingCanOverflow = ~msd === 0; - if (roundingCanOverflow) resultLength++; - } - let result = new JSBI(resultLength, sign); - if (bitsShift === 0) { - for (let i = digitShift; i < length; i++) { - result.__setDigit(i - digitShift, x.__digit(i)); - } - } else { - let carry = x.__digit(digitShift) >>> bitsShift; - const last = length - digitShift - 1; - for (let i = 0; i < last; i++) { - const d = x.__digit(i + digitShift + 1); - result.__setDigit(i, (d << (32 - bitsShift)) | carry); - carry = d >>> bitsShift; - } - result.__setDigit(last, carry); - } - if (mustRoundDown) { - // Since the result is negative, rounding down means adding one to its - // absolute value. This cannot overflow. - result = JSBI.__absoluteAddOne(result, true, result); - } - return result.__trim(); - } - - static __rightShiftByMaximum(sign) { - if (sign) { - return JSBI.__oneDigit(1, true); - } - return JSBI.__zero(); - } - - static __toShiftAmount(x) { - if (x.length > 1) return -1; - const value = x.__unsignedDigit(0); - if (value > JSBI.__kMaxLengthBits) return -1; - return value; - } - - static __toPrimitive(obj, hint='default') { - if (typeof obj !== 'object') return obj; - if (obj.constructor === JSBI) return obj; - const exoticToPrim = obj[Symbol.toPrimitive]; - if (exoticToPrim) { - const primitive = exoticToPrim(hint); - if (typeof primitive !== 'object') return primitive; - throw new TypeError('Cannot convert object to primitive value'); - } - const valueOf = obj.valueOf; - if (valueOf) { - const primitive = valueOf.call(obj); - if (typeof primitive !== 'object') return primitive; - } - const toString = obj.toString; - if (toString) { - const primitive = toString.call(obj); - if (typeof primitive !== 'object') return primitive; - } - throw new TypeError('Cannot convert object to primitive value'); - } - - static __toNumeric(value) { - if (JSBI.__isBigInt(value)) return value; - return +value; - } - - static __isBigInt(value) { - return typeof value === 'object' && value.constructor === JSBI; - } - - // Digit helpers. - __digit(i) { - return this[i]; - } - __unsignedDigit(i) { - return this[i] >>> 0; - } - __setDigit(i, digit) { - this[i] = digit | 0; - } - __setDigitGrow(i, digit) { - this[i] = digit | 0; - } - __halfDigitLength() { - const len = this.length; - if (this.__unsignedDigit(len - 1) <= 0xFFFF) return len * 2 - 1; - return len*2; - } - __halfDigit(i) { - return (this[i >>> 1] >>> ((i & 1) << 4)) & 0xFFFF; - } - __setHalfDigit(i, value) { - const digitIndex = i >>> 1; - const previous = this.__digit(digitIndex); - const updated = (i & 1) ? (previous & 0xFFFF) | (value << 16) - : (previous & 0xFFFF0000) | (value & 0xFFFF); - this.__setDigit(digitIndex, updated); - } - - static __digitPow(base, exponent) { - let result = 1; - while (exponent > 0) { - if (exponent & 1) result *= base; - exponent >>>= 1; - base *= base; - } - return result; - } -} - -JSBI.__kMaxLength = 1 << 25; -JSBI.__kMaxLengthBits = JSBI.__kMaxLength << 5; -// Lookup table for the maximum number of bits required per character of a -// base-N string representation of a number. To increase accuracy, the array -// value is the actual value multiplied by 32. To generate this table: -// -// for (let i = 0; i <= 36; i++) { -// console.log(Math.ceil(Math.log2(i) * 32) + ','); -// } -JSBI.__kMaxBitsPerChar = [ - 0, 0, 32, 51, 64, 75, 83, 90, 96, // 0..8 - 102, 107, 111, 115, 119, 122, 126, 128, // 9..16 - 131, 134, 136, 139, 141, 143, 145, 147, // 17..24 - 149, 151, 153, 154, 156, 158, 159, 160, // 25..32 - 162, 163, 165, 166, // 33..36 -]; -JSBI.__kBitsPerCharTableShift = 5; -JSBI.__kBitsPerCharTableMultiplier = 1 << JSBI.__kBitsPerCharTableShift; -JSBI.__kConversionChars = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''); -JSBI.__kBitConversionBuffer = new ArrayBuffer(8); -JSBI.__kBitConversionDouble = new Float64Array(JSBI.__kBitConversionBuffer); -JSBI.__kBitConversionInts = new Int32Array(JSBI.__kBitConversionBuffer); diff --git a/numbercruncher-bigint.js b/numbercruncher-bigint.js deleted file mode 100644 index 8d21c75..0000000 --- a/numbercruncher-bigint.js +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2019 Oliver Lau , Heise Medien GmbH & Co. KG -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -'use strict'; - -importScripts('shunting-yard.js', 'worker-message-handler.js'); - -const TRUE = 1n, FALSE = 0n; - -Token.Functions = { - max: { - f: (...[a, b]) => a > b ? a : b, - n: 2 - }, - min: { - f: (...[a, b]) => a < b ? a : b, - n: 2 - }, - popcnt: { - f: (...[a]) => { - let popcnt = 0n; - while (a > 0n) { - if ((a & 1n) === 1n) { - ++popcnt; - } - a >>= 1n; - } - return popcnt; - }, - n: 1 - }, - gcd: { - f: (...[a, b]) => { - if (a === 0n || b === 0n) { - return 0n; - } - while (b !== 0n) { - [a, b] = [b, a % b]; - } - return a; - }, - n: 2 - }, - lcm: { - f: (...[a, b]) => { - if (a === 0n || b === 0n) { - return 0n; - } - return a * b / Token.Functions.gcd.f(a, b); - }, - n: 2 - }, - sign: { - f: (...[a]) => a < 0n ? -1n : 1n, - n: 1 - }, - sqrt: { - f: (...[a]) => { - if (a < 0n) { - throw 'cannot calculate square root of negative numbers'; - } - else if (a < 2n) { - return a; - } - let shift = 2n; - let nShifted = a >> shift; - while (nShifted !== 0n && nShifted !== a) { - shift += 2n; - nShifted = a >> shift; - } - shift -= 2n; - let result = 0n; - while (shift >= 0n) { - result <<= 1n; - const candidateResult = result + 1n; - if ((candidateResult * candidateResult) <= (a >> shift)) { - result = candidateResult; - } - shift -= 2n; - } - return result; - }, - n: 1 - }, -}; - -class Calculator { - constructor() { - this.variables = {}; - } - - calculate(expr) { - const { tokens, error } = tokenize(expr); - if (error) { - return { error: error }; - } - if (tokens) { - const s = new Stack(); - const rpnTokens = shuntingYard(tokens); - for (const t of rpnTokens) { - if (t.type === Token.Type.Literal || t.type === Token.Type.Variable) { - s.push(t); - } - else if (t.type === Token.Type.Function) { - const n = Token.Functions[t.value].n; - const f = Token.Functions[t.value].f; - if (s.length < n) { - return { result: undefined }; - } - const args = (function(s, n) { - let args = []; - for (let i = 0; i < n; ++i) { - const token = s.pop(); - if (token instanceof Token) { - const value = (token.type === Token.Type.Literal) - ? token.value - : this.variables[token.value]; - if (typeof value === 'bigint') { - args.unshift(value); - } - else { - return { error: `undefined variable '${token.value}'` }; - } - } - else { - return { error: 'illegal token' }; - } - } - return args; - }.bind(this))(s, n); - if (args instanceof Array) { - try { - const r = f(...args); - s.push(new Token(Token.Type.Literal, r)); - } - catch (e) { - return { error: e }; - } - } - else { - return { error: args.error }; - } - } - else if (t.type === Token.Type.Operator && t.value === Token.Symbols.UnaryMinus) { - if (s.top) { - switch (s.top.type) { - case Token.Type.Literal: - s.top.value = -s.top.value; - break; - case Token.Type.Variable: - s.top.value = -this.variables[s.top.value]; - s.top.type = Token.Type.Literal; - break; - default: - break; - } - } - } - else { - const bToken = s.pop(); - const aToken = s.pop(); - if (aToken instanceof Token && bToken instanceof Token) { - if (bToken.type === Token.Type.Variable && !this.variables.hasOwnProperty(bToken.value)) { - return { error: `undefined variable '${bToken.value}'` }; - } - const a = (aToken.type === Token.Type.Literal) - ? aToken.value - : this.variables[aToken.value]; - const b = (bToken.type === Token.Type.Literal) - ? bToken.value - : this.variables[bToken.value]; - let r; - try { - switch (t.value) { - case '=': this.variables[aToken.value] = b; break; - case '+=': this.variables[aToken.value] = a + b; break; - case '-=': this.variables[aToken.value] = a - b; break; - case '/=': this.variables[aToken.value] = a / b; break; - case '%=': this.variables[aToken.value] = a % b; break; - case '*=': this.variables[aToken.value] = a * b; break; - case '<<=': this.variables[aToken.value] = a << b; break; - case '>>=': this.variables[aToken.value] = a >> b; break; - case '&=': this.variables[aToken.value] = a & b; break; - case '|=': this.variables[aToken.value] = a | b; break; - case '^=': this.variables[aToken.value] = a ^ b; break; - case '+': r = a + b; break; - case '-': r = a - b; break; - case '*': r = a * b; break; - case '**': r = a ** b; break; - case '/': r = a / b; break; - case '%': r = a % b; break; - case '^': r = a ^ b; break; - case '|': r = a | b; break; - case '&': r = a & b; break; - case '<<': r = a << b; break; - case '>>': r = a >> b; break; - case '<': r = a < b ? TRUE : FALSE; break; - case '>': r = a > b ? TRUE : FALSE; break; - case '<=': r = a <= b ? TRUE : FALSE; break; - case '>=': r = a >= b ? TRUE : FALSE; break; - case '==': r = a == b ? TRUE : FALSE; break; - case '!=': r = a != b ? TRUE : FALSE; break; - case ',': break; - default: return { error: `unknown operator: ${t.value}` }; - } - } - catch (e) { - return { error: `${e.name}: ${e.message || ''}` }; - } - if (typeof r === 'bigint') { - s.push(new Token(Token.Type.Literal, r)); - } - } - } - } - if (s.length === 1) { - if (s.top.type === Token.Type.Variable) { - if (this.variables.hasOwnProperty(s.top.value)) { - return { result: this.variables[s.top.value] }; - } - else { - return { error: `undefined variable '${s.top.value}'` } - } - } - return { result: s.top.value }; - } - else { - return { result: undefined }; - } - } - return { error: 'invalid expression' }; - } -} diff --git a/numbercruncher-jsbi.js b/numbercruncher-jsbi.js deleted file mode 100644 index 126b263..0000000 --- a/numbercruncher-jsbi.js +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) 2019 Oliver Lau , Heise Medien GmbH & Co. KG -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -'use strict'; - -importScripts('jsbi.js', 'shunting-yard.js', 'worker-message-handler.js'); - -const TRUE = JSBI.BigInt(1), FALSE = JSBI.BigInt(0); - -Token.Functions = { - max: { - f: (...[a, b]) => JSBI.greaterThan(a, b) ? a : b, - n: 2 - }, - min: { - f: (...[a, b]) => JSBI.lessThan(a, b) ? a : b, - n: 2 - }, - popcnt: { - f: (...[a]) => { - let popcnt = JSBI.__zero(); - const One = JSBI.BigInt(1); - while (a > JSBI.__zero()) { - if ((a & One) === One) { - ++popcnt; - } - a = JSBI.signedRightShift(a, One); - } - return popcnt; - }, - n: 1 - }, - gcd: { - f: (...[a, b]) => { - if (JSBI.equal(a, JSBI.__zero()) || JSBI.equal(b, JSBI.__zero())) { - return JSBI.__zero(); - } - while (!JSBI.equal(b, JSBI.__zero())) { - [a, b] = [b, JSBI.remainder(a, b)]; - } - return a; - }, - n: 2 - }, - lcm: { - f: (...[a, b]) => { - if (JSBI.equal(a, JSBI.__zero()) || JSBI.equal(b, JSBI.__zero())) { - return JSBI.__zero(); - } - return JSBI.divide(JSBI.multiply(a, b), Token.Functions.gcd.f(a, b)); - }, - n: 2 - }, - sign: { - f: (...[a]) => JSBI.lessThan(a, JSBI.__zero()) ? JSBI.BigInt(-1) : JSBI.BigInt(1), - n: 1 - }, - sqrt: { - f: (...[a]) => { - const One = JSBI.BigInt(1); - const Two = JSBI.BigInt(2); - if (JSBI.lessThan(a, JSBI.__zero())) { - throw 'cannot calculate square root of negative numbers'; - } - else if (JSBI.lessThan(a, Two)) { - return a; - } - let shift = Two; - let nShifted = JSBI.signedRightShift(a, shift); - while (!JSBI.equal(nShifted, JSBI.__zero()) && !JSBI.equal(nShifted, a)) { - shift = JSBI.add(shift, Two); - nShifted = JSBI.signedRightShift(a, shift); - } - shift = JSBI.subtract(shift, Two); - let result = JSBI.__zero(); - while (JSBI.greaterThanOrEqual(shift, JSBI.__zero())) { - result = JSBI.leftShift(result, One); - const candidateResult = JSBI.add(result, One); - if (JSBI.lessThanOrEqual(JSBI.multiply(candidateResult, candidateResult), JSBI.signedRightShift(a, shift))) { - result = candidateResult; - } - shift = JSBI.subtract(shift, Two); - } - return result; - }, - n: 1 - }, -}; - - -class Calculator { - - constructor() { - this._variables = {}; - } - - get variables() { - return this._variables; - } - - calculate(expr) { - const { tokens, error } = tokenize(expr); - if (error) { - return { error }; - } - if (tokens) { - const s = new Stack(); - const rpnTokens = shuntingYard(tokens); - for (const t of rpnTokens) { - if (t.type === Token.Type.Literal || t.type === Token.Type.Variable) { - s.push(t); - } - else if (t.type === Token.Type.Function) { - const n = Token.Functions[t.value].n; - const f = Token.Functions[t.value].f; - if (s.length < n) { - return { result: undefined }; - } - const args = (function(s, n) { - let args = []; - for (let i = 0; i < n; ++i) { - const token = s.pop(); - if (token instanceof Token) { - const value = (token.type === Token.Type.Literal) - ? token.value - : this._variables[token.value]; - if (value instanceof Array) { - args.unshift(value); - } - else { - return { error: `***undefined variable '${token.value}'` }; - } - } - else { - return { error: 'illegal token' }; - } - } - return args; - })(s, n); - if (args instanceof Array) { - try { - const r = f(...args); - s.push(new Token(Token.Type.Literal, r)); - } - catch (error) { - return { error }; - } - } - else { - return { error: args.error }; - } - } - else if (t.type === Token.Type.Operator && t.value === Token.Symbols.UnaryMinus) { - if (s.top) { - switch (s.top.type) { - case Token.Type.Literal: - s.top.value = JSBI.unaryMinus(s.top.value); - break; - case Token.Type.Variable: - s.top.value = JSBI.unaryMinus(this._variables[s.top.value]); - s.top.type = Token.Type.Literal; - break; - default: - break; - } - } - } - else { - const bToken = s.pop(); - const aToken = s.pop(); - if (aToken instanceof Token && bToken instanceof Token) { - if (bToken.type === Token.Type.Variable && !this._variables.hasOwnProperty(bToken.value)) { - return { error: `undefined variable '${bToken.value}'` }; - } - const a = (aToken.type === Token.Type.Literal) - ? aToken.value - : this._variables[aToken.value]; - const b = (bToken.type === Token.Type.Literal) - ? bToken.value - : this._variables[bToken.value]; - let r; - try { - switch (t.value) { - case '=': this._variables[aToken.value] = b; break; - case '+=': this._variables[aToken.value] = JSBI.add(a, b); break; - case '-=': this._variables[aToken.value] = JSBI.subtract(a, b); break; - case '/=': this._variables[aToken.value] = JSBI.divide(a, b); break; - case '%=': this._variables[aToken.value] = JSBI.remainder(a, b); break; - case '*=': this._variables[aToken.value] = JSBI.multiply(a, b); break; - case '<<=': this._variables[aToken.value] = JSBI.leftShift(a, b); break; - case '>>=': this._variables[aToken.value] = JSBI.signedRightShift(a, b); break; - case '&=': this._variables[aToken.value] = JSBI.bitwiseAnd(a, b); break; - case '|=': this._variables[aToken.value] = JSBI.bitwiseOr(a, b); break; - case '^=': this._variables[aToken.value] = JSBI.bitwiseXor(a, b); break; - case '+': r = JSBI.add(a, b); break; - case '-': r = JSBI.subtract(a, b); break; - case '*': r = JSBI.multiply(a, b); break; - case '**': r = JSBI.exponentiate(a, b); break; - case '/': r = JSBI.divide(a, b); break; - case '%': r = JSBI.remainder(a, b); break; - case '^': r = JSBI.bitwiseXor(a, b); break; - case '|': r = JSBI.bitwiseOr(a, b); break; - case '&': r = JSBI.bitwiseAnd(a, b); break; - case '<<': r = JSBI.leftShift(a, b); break; - case '>>': r = JSBI.signedRightShift(a, b); break; - case '<': r = JSBI.lessThan(a, b) ? TRUE : FALSE; break; - case '>': r = JSBI.greaterThan(a, b) ? TRUE : FALSE; break; - case '<=': r = JSBI.lessThanOrEqual(a, b) ? TRUE : FALSE; break; - case '>=': r = JSBI.greaterThanOrEqual(a, b) ? TRUE : FALSE; break; - case '==': r = JSBI.equal(a, b) ? TRUE : FALSE; break; - case '!=': r = JSBI.equal(a, b) ? FALSE : TRUE; break; - case ',': break; - default: return { error: `unknown operator: ${t.value}` }; - } - } - catch (e) { - return { error: `invalid expression (${e.name}) ${e.message || ''}` }; - } - if (r instanceof Array) { - s.push(new Token(Token.Type.Literal, r)); - } - } - } - } - if (s.length === 1) { - if (s.top.type === Token.Type.Variable) { - if (this._variables.hasOwnProperty(s.top.value)) { - return { result: this._variables[s.top.value] }; - } - else { - return { error: `undefined variable '${s.top.value}'` } - } - } - return { result: s.top.value }; - } - else { - return { result: undefined }; - } - } - return { error: 'invalid expression' }; - } -} diff --git a/numbercruncher.js b/numbercruncher.js new file mode 100644 index 0000000..15afd4d --- /dev/null +++ b/numbercruncher.js @@ -0,0 +1,503 @@ +// Copyright (c) 2019-2024 Oliver Lau , Heise Medien GmbH & Co. KG +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +if (typeof window === 'object') + import('./worker-message-handler.js'); + +// Set or get the last element in an array. +Object.defineProperty(Array.prototype, 'last', { + get: function () { return this[this.length - 1]; }, + set: function (v) { this[this.length - 1] = v; }, +}); + +class Stack { + constructor() { + this._stack = new Array(); + this._nextIndex = 0; + } + push(value) { this._stack.push(value); } + pop() { return this._stack.pop(); } + get top() { return this._stack.last; } + notEmpty() { return this._stack.length > 0; } + set top(v) { this._stack.last = v; } + get length() { return this._stack.length; } + get stack() { return this._stack; } +} + +class Queue { + constructor() { + this._queue = new Array(); + this._nextIndex = 0; + } + push(value) { this._queue.push(value); } + pop() { return this._queue.pop(); } + get length() { return this._queue.length; } + get top() { return this._queue.last; } + values() { return this._queue; } + [Symbol.iterator]() { + return { + next: () => { + if (this._nextIndex < this.length) { + return { + value: this._queue[this._nextIndex++], + done: false, + }; + } + this._nextIndex = 0; + return { + done: true, + }; + } + } + } +} + +// Token represents an operator, variable or constant value. +class Token { + constructor(type, value) { + this._type = type; + this._value = value; + } + get type() { return this._type; } + get value() { return this._value; } + set type(t) { this._type = t; } + set value(v) { this._value = v; } + get precedence() { return Token.OperatorPrecAssoc[this.value].prec; } + get associativity() { return Token.OperatorPrecAssoc[this.value].assoc; } + equals(other) { + return this._type === other.type + && this._value === other.value; + } +} + +// Token.Type is an object containing the name of a token (key) +// with an associated value. +Token.Type = Object.freeze({ + Literal: Symbol('Literal'), + Operator: Symbol('Operator'), + Function: Symbol('Function'), + Variable: Symbol('Variable'), + LeftParenthesis: Symbol('LeftParenthesis'), + RightParenthesis: Symbol('RightParenthesis'), +}); + +Token.BasePrefix = Object.freeze({ 2: '0b', 8: '0o', 10: '', 16: '0x' }); + +// Token.Operators contains the list of valid operators. +Token.Operators = Object.freeze([ + '~=', '~', '&=', '^=', '/=', '%=', '+=', '-=', '<<=', '>>=', + '^', '&', '|', '+', '-', + '**', '*', '/', '%', + '<<', '>>', + '==', '!=', '<=', '>=', + '>', '<', '=', ',']); + +// There are special operators which are not recognized by the +// tokenizer but later on represent a symbol with a new meaning. +// Currently, only the unary minus belongs to these operators. +Token.Symbols = Object.freeze({ UnaryMinus: '\u{2212}' }); + +const ASSOC = Object.freeze({ + LEFT: -1, + RIGHT: +1, +}); + +// Token.Operator defines the precedence and associativity of +// all operators recognized by the calculator. +Token.OperatorPrecAssoc = { + '\u2212': { prec: -3, assoc: ASSOC.RIGHT }, // unary minus + '~': { prec: -3, assoc: ASSOC.RIGHT }, + '**': { prec: -4, assoc: ASSOC.RIGHT }, + '*': { prec: -5, assoc: ASSOC.LEFT }, + '/': { prec: -5, assoc: ASSOC.LEFT }, + '%': { prec: -5, assoc: ASSOC.LEFT }, + '+': { prec: -6, assoc: ASSOC.LEFT }, + '-': { prec: -6, assoc: ASSOC.LEFT }, + '<<': { prec: -7, assoc: ASSOC.LEFT }, + '>>': { prec: -7, assoc: ASSOC.LEFT }, + '<': { prec: -9, assoc: ASSOC.LEFT }, + '>': { prec: -9, assoc: ASSOC.LEFT }, + '==': { prec: -9, assoc: ASSOC.LEFT }, + '!=': { prec: -9, assoc: ASSOC.LEFT }, + '<=': { prec: -9, assoc: ASSOC.LEFT }, + '>=': { prec: -9, assoc: ASSOC.LEFT }, + '&': { prec: -11, assoc: ASSOC.LEFT }, + '^': { prec: -12, assoc: ASSOC.LEFT }, + '|': { prec: -13, assoc: ASSOC.LEFT }, + '=': { prec: -16, assoc: ASSOC.RIGHT }, + '&=': { prec: -16, assoc: ASSOC.RIGHT }, + '^=': { prec: -16, assoc: ASSOC.RIGHT }, + '|=': { prec: -16, assoc: ASSOC.RIGHT }, + '+=': { prec: -16, assoc: ASSOC.RIGHT }, + '-=': { prec: -16, assoc: ASSOC.RIGHT }, + '*=': { prec: -16, assoc: ASSOC.RIGHT }, + '/=': { prec: -16, assoc: ASSOC.RIGHT }, + '%=': { prec: -16, assoc: ASSOC.RIGHT }, + ',': { prec: -17, assoc: ASSOC.LEFT }, +}; + +const AvailableFunctions = { + max: { + f: (...[a, b]) => a > b ? a : b, + n: 2, + }, + min: { + f: (...[a, b]) => a < b ? a : b, + n: 2, + }, + popcnt: { + f: (...[a]) => { + let popcnt = 0n; + while (a > 0n) { + if ((a & 1n) === 1n) { + ++popcnt; + } + a >>= 1n; + } + return popcnt; + }, + n: 1, + }, + gcd: { + f: (...[a, b]) => { + if (a === 0n || b === 0n) { + return 0n; + } + while (b !== 0n) { + [a, b] = [b, a % b]; + } + return a; + }, + n: 2, + }, + lcm: { + f: (...[a, b]) => { + if (a === 0n || b === 0n) { + return 0n; + } + return a * b / AvailableFunctions.gcd.f(a, b); + }, + n: 2, + }, + sign: { + f: (...[a]) => a < 0n ? -1n : 1n, + n: 1, + }, + sqrt: { + f: (...[a]) => { + if (a < 0n) { + throw 'cannot calculate square root of negative numbers'; + } + else if (a < 2n) { + return a; + } + let shift = 2n; + let nShifted = a >> shift; + while (nShifted !== 0n && nShifted !== a) { + shift += 2n; + nShifted = a >> shift; + } + shift -= 2n; + let result = 0n; + while (shift >= 0n) { + result <<= 1n; + const candidateResult = result + 1n; + if ((candidateResult * candidateResult) <= (a >> shift)) { + result = candidateResult; + } + shift -= 2n; + } + return result; + }, + n: 1, + }, +}; + +// All functions defined in AvailableFunctions have the highest precedence +// of -1 and are right-associative. +Object.keys(AvailableFunctions).forEach(f => { + Token.OperatorPrecAssoc[f] = { prec: -1, assoc: ASSOC.RIGHT }; +}); +Object.freeze(Token.OperatorPrecAssoc); + + +// To discover the type of each token found in the expression +// regex's are used. +Token.Types = Object.freeze([ + { regex: new RegExp(`^(${Object.keys(AvailableFunctions).join('|')})`), type: Token.Type.Function, name: 'function' }, + { regex: new RegExp(`^(${Token.Operators.map(o => o.replace(/[\|\-\/\*\+\^\$]/g, '\\$&')).join('|')})`), type: Token.Type.Operator, name: 'operator' }, + { regex: /^b([01]+)/, type: Token.Type.Literal, base: 2, name: 'binary' }, + { regex: /^o([0-7]+)/, type: Token.Type.Literal, base: 8, name: 'octal' }, + { regex: /^([0-9]+)/, type: Token.Type.Literal, base: 10, name: 'decimal' }, + { regex: /^x([0-9a-fA-F]+)/, type: Token.Type.Literal, base: 16, name: 'hexadecimal' }, + { regex: /^([a-zA-Z_][a-zA-Z0-9_]*)/, type: Token.Type.Variable, name: 'variable' }, + { regex: /^(\()/, type: Token.Type.LeftParenthesis, name: 'left parenthesis' }, + { regex: /^(\))/, type: Token.Type.RightParenthesis, name: 'right parenthesis' }, +]); + + +class ShuntingYard { + // tokenize() parses the expression into tokens of type Token. + static tokenize(expr) { + let tokens = []; + while (expr.length > 0) { + let found = false; + for (let i = 0; i < Token.Types.length && !found; ++i) { + const t = Token.Types[i]; + const m = expr.match(t.regex); + if (m && m.length > 0) { + const len = m[0].length; + let symbol = m[1]; + let value; + switch (t.type) { + case Token.Type.Literal: + try { + value = BigInt(Token.BasePrefix[t.base] + symbol); + } + catch (error) { + return { error }; + } + break; + default: + if (symbol === '-' + && + ( + tokens.length === 0 || + tokens.last.type === Token.Type.LeftParenthesis || + Token.Operators.indexOf(tokens.last.value) >= 0 + ) + ) { + symbol = Token.Symbols.UnaryMinus; + } + value = symbol; + break; + } + tokens.push(new Token(t.type, value)); + expr = expr.substring(len); + found = true; + } + } + if (!found) { + return { error: `invalid expression: ${expr}` }; + } + } + return { tokens }; + } + + // The Shunting-Yard algorithm takes a list of tokens and rearranges them + // into an array with the tokens in Reverse Polish Notation. + static shunt(tokens) { + const ops = new Stack(); + const queue = new Queue(); + for (const token of tokens) { + switch (token.type) { + case Token.Type.Literal: + // fall-through + case Token.Type.Variable: + queue.push(token); + break; + case Token.Type.LeftParenthesis: + ops.push(token); + break; + case Token.Type.RightParenthesis: + while (ops.notEmpty() && ops.top.type !== Token.Type.LeftParenthesis) { + queue.push(ops.pop()); + } + if (ops.top.type === Token.Type.LeftParenthesis) { + ops.pop(); + } + break; + case Token.Type.Function: + // TODO + case Token.Type.Operator: + while (ops.notEmpty() && + (ops.top.type !== Token.Type.LeftParenthesis) + && + ( + (ops.top.precedence > token.precedence) + || + (ops.top.precedence === token.precedence && ops.top.associativity === ASSOC.LEFT) + ) + ) { + queue.push(ops.pop()); + } + if (token.value !== ',') { + ops.push(token); + } + break; + default: + break; + } + } + while (ops.length > 0) { + queue.push(ops.pop()); + } + return queue; + } +}; + + +class Calculator { + constructor() { + this.variables = {}; + } + + static TRUE = 1n; + static FALSE = 0n; + + calculate(expr) { + const { tokens, error } = ShuntingYard.tokenize(expr); + if (error) { + return { error: error }; + } + if (tokens.length === 0) { + return { error: 'invalid expression' }; + } + console.log(JSON.stringify(tokens, 2)); + const rpnTokens = ShuntingYard.shunt(tokens); + console.log(JSON.stringify(rpnTokens, 2)); + const s = new Queue(); + for (const t of rpnTokens) { + if (t.type === Token.Type.Literal || t.type === Token.Type.Variable) { + s.push(t); + } + else if (t.type === Token.Type.Function) { + const n = AvailableFunctions[t.value].n; + const f = AvailableFunctions[t.value].f; + if (s.length < n) { + return { result: undefined }; + } + const args = (function (s, n) { + let args = []; + for (let i = 0; i < n; ++i) { + const token = s.pop(); + if (token instanceof Token) { + const value = (token.type === Token.Type.Literal) + ? token.value + : this.variables[token.value]; + if (typeof value === 'bigint') { + args.unshift(value); + } + else { + return { error: `undefined variable '${token.value}'` }; + } + } + else { + return { error: 'illegal token' }; + } + } + return args; + }.bind(this))(s, n); + if (args instanceof Array) { + try { + const r = f(...args); + s.push(new Token(Token.Type.Literal, r)); + } + catch (e) { + return { error: e }; + } + } + else { + return { error: args.error }; + } + } + else if (t.type === Token.Type.Operator && t.value === Token.Symbols.UnaryMinus) { + if (s.notEmpty()) { + switch (s.top.type) { + case Token.Type.Literal: + s.top.value = -s.top.value; + break; + case Token.Type.Variable: + s.top.value = -this.variables[s.top.value]; + s.top.type = Token.Type.Literal; + break; + default: + break; + } + } + } + else { + const bToken = s.pop(); + const aToken = s.pop(); + if (aToken instanceof Token && bToken instanceof Token) { + if (bToken.type === Token.Type.Variable && !this.variables.hasOwnProperty(bToken.value)) { + return { error: `undefined variable '${bToken.value}'` }; + } + const a = (aToken.type === Token.Type.Literal) + ? aToken.value + : this.variables[aToken.value]; + const b = (bToken.type === Token.Type.Literal) + ? bToken.value + : this.variables[bToken.value]; + let r; + try { + switch (t.value) { + case '=': this.variables[aToken.value] = b; break; + case '+=': this.variables[aToken.value] = a + b; break; + case '-=': this.variables[aToken.value] = a - b; break; + case '/=': this.variables[aToken.value] = a / b; break; + case '%=': this.variables[aToken.value] = a % b; break; + case '*=': this.variables[aToken.value] = a * b; break; + case '<<=': this.variables[aToken.value] = a << b; break; + case '>>=': this.variables[aToken.value] = a >> b; break; + case '&=': this.variables[aToken.value] = a & b; break; + case '|=': this.variables[aToken.value] = a | b; break; + case '^=': this.variables[aToken.value] = a ^ b; break; + case '+': r = a + b; break; + case '-': r = a - b; break; + case '*': r = a * b; break; + case '**': r = a ** b; break; + case '/': r = a / b; break; + case '%': r = a % b; break; + case '^': r = a ^ b; break; + case '|': r = a | b; break; + case '&': r = a & b; break; + case '<<': r = a << b; break; + case '>>': r = a >> b; break; + case '<': r = a < b ? Calculator.TRUE : Calculator.FALSE; break; + case '>': r = a > b ? Calculator.TRUE : Calculator.FALSE; break; + case '<=': r = a <= b ? Calculator.TRUE : Calculator.FALSE; break; + case '>=': r = a >= b ? Calculator.TRUE : Calculator.FALSE; break; + case '==': r = a == b ? Calculator.TRUE : Calculator.FALSE; break; + case '!=': r = a != b ? Calculator.TRUE : Calculator.FALSE; break; + case ',': break; + default: return { error: `unknown operator: ${t.value}` }; + } + } + catch (e) { + return { error: `${e.name}: ${e.message || ''}` }; + } + if (typeof r === 'bigint') { + s.push(new Token(Token.Type.Literal, r)); + } + } + } + } + if (s.length === 1) { + if (s.top.type === Token.Type.Variable) { + if (this.variables.hasOwnProperty(s.top.value)) { + return { result: this.variables[s.top.value] }; + } + else { + return { error: `undefined variable '${s.top.value}'` } + } + } + return { result: s.top.value }; + } + else { + return { result: undefined }; + } + } +} diff --git a/shunting-yard.js b/shunting-yard.js deleted file mode 100644 index 6fd9e3a..0000000 --- a/shunting-yard.js +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (c) 2019 Oliver Lau , Heise Medien GmbH & Co. KG -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -'use strict'; - -// Set or get the last element in an array. -Object.defineProperty(Array.prototype, 'last', { - get: function() { return this[this.length-1]; }, - set: function(v) { this[this.length-1] = v; }, -}); - -// Stack represents a classic stack from which you can pop items -// or push items onto it. -class Stack { - constructor() { - this._stack = []; - this._nextIndex = 0; - } - push(value) { this._stack.push(value); } - pop() { return this._stack.pop(); } - get top() { return this._stack.last; } - set top(v) { this._stack.last = v; } - get length() { return this._stack.length; } - get stack() { return this._stack; } - [Symbol.iterator]() { - return { - next: () => { - if (this._nextIndex < this.length) { - return { - value: this._stack[this._nextIndex++], - done: false - }; - } - this._nextIndex = 0; - return { - done: true - }; - } - } - } -} - -// LEFT and RIGHT are constants to determine whether a token -// in an expression is left- or right-associative. -const LEFT = -1, RIGHT = +1; - -// Token represents an operator, variable or constant value. -class Token { - constructor(type, value) { - this._type = type; - this._value = value; - } - get type() { return this._type; } - get value() { return this._value; } - set type(t) { this._type = t; } - set value(v) { this._value = v; } - get precedence() { return Token.OperatorPrecAssoc[this.value].prec; } - get associativity() { return Token.OperatorPrecAssoc[this.value].assoc; } - equals(other) { - return this._type === other.type - && this._value === other.value; - } -} - -// Token.Type is an object containing the name of a token (key) -// with an associated value. -Token.Type = (types => { - let obj = {}; - for (const i in types) { - obj[types[i]] = +i + 1; - } - return obj; -})(['Literal', 'Operator', 'Function', 'Variable', 'LeftParenthesis', 'RightParenthesis']); -Object.freeze(Token.Type); - -Token.BasePrefix = { 2: '0b', 8: '0o', 10: '', 16: '0x' }; -Object.freeze(Token.BasePrefix); - -// Token.Operators contains the list of valid operators. -Token.Operators = ['~=', '~', '&=', '^=', '/=', '%=', '+=', '-=', '<<=', '>>=', '^', '&', '|', '+', '-', '**', '*', '/', '%', '<<', '>>', '==', '!=', '<=', '>=', '>', '<', '=', ',']; -Object.freeze(Token.Operators); - -// There are special operators which are not recognized by the -// tokenizer but later on must represent a symbol with a new meaning. -// Currently, only the unary minus belongs to these operators. -Token.Symbols = { UnaryMinus: '\u{2212}' }; -Object.freeze(Token.Symbols); - -// Token.Operator defines the precedence and associativity of -// all valid operators -Token.OperatorPrecAssoc = { - '\u2212': { prec: -3, assoc: RIGHT }, // unary minus - '~': { prec: -3, assoc: RIGHT }, - '**': { prec: -4, assoc: RIGHT }, - '*': { prec: -5, assoc: LEFT }, - '/': { prec: -5, assoc: LEFT }, - '%': { prec: -5, assoc: LEFT }, - '+': { prec: -6, assoc: LEFT }, - '-': { prec: -6, assoc: LEFT }, - '<<': { prec: -7, assoc: LEFT }, - '>>': { prec: -7, assoc: LEFT }, - '<': { prec: -9, assoc: LEFT }, - '>': { prec: -9, assoc: LEFT }, - '==': { prec: -9, assoc: LEFT }, - '!=': { prec: -9, assoc: LEFT }, - '<=': { prec: -9, assoc: LEFT }, - '>=': { prec: -9, assoc: LEFT }, - '&': { prec: -11, assoc: LEFT }, - '^': { prec: -12, assoc: LEFT }, - '|': { prec: -13, assoc: LEFT }, - '=': { prec: -16, assoc: RIGHT }, - '&=': { prec: -16, assoc: RIGHT }, - '^=': { prec: -16, assoc: RIGHT }, - '|=': { prec: -16, assoc: RIGHT }, - '+=': { prec: -16, assoc: RIGHT }, - '-=': { prec: -16, assoc: RIGHT }, - '*=': { prec: -16, assoc: RIGHT }, - '/=': { prec: -16, assoc: RIGHT }, - '%=': { prec: -16, assoc: RIGHT }, - ',': { prec: -17, assoc: LEFT }, -}; - - -// Depending on whether BigInt or JSBI.BigInt is supported there are -// predefined functions you can use within Arbitrary Precision Calculator. -// You cand find the actual implementations in the respective -// numbercruncher-*.js file. -Token.Functions = { - max: null, - min: null, - gcd: null, - lcm: null, - sign: null, - sqrt: null, - popcnt: null, -}; - -// All functions defined in Token.Functions have the highest precedence -// of -1 and are right-associative. -Object.keys(Token.Functions).forEach(f => { - Token.OperatorPrecAssoc[f] = { prec: -1, assoc: RIGHT }; -}); - -Object.freeze(Token.OperatorPrecAssoc); - - -// To discover the type of each token found in the expression -// regex's are used. -Token.Types = [ - { regex: new RegExp(`^(${Object.keys(Token.Functions).join('|')})`), type: Token.Type.Function, name: 'function' }, - { regex: new RegExp(`^(${Token.Operators.map(o => o.replace(/[\|\-\/\*\+\^\$]/g, '\\$&')).join('|')})`), type: Token.Type.Operator, name: 'operator' }, - { regex: /^b([01]+)/, type: Token.Type.Literal, base: 2, name: 'binary' }, - { regex: /^o([0-7]+)/, type: Token.Type.Literal, base: 8, name: 'octal' }, - { regex: /^([0-9]+)/, type: Token.Type.Literal, base: 10, name: 'decimal' }, - { regex: /^x([0-9a-fA-F]+)/, type: Token.Type.Literal, base: 16, name: 'hexadecimal' }, - { regex: /^([a-zA-Z_][a-zA-Z0-9_]*)/, type: Token.Type.Variable, name: 'variable' }, - { regex: /^(\()/, type: Token.Type.LeftParenthesis, name: 'left parenthesis' }, - { regex: /^(\))/, type: Token.Type.RightParenthesis, name: 'right parenthesis' }, -]; -Object.freeze(Token.Types); - -// tokenize() parses the expression into tokens of type Token. -const tokenize = expr => { - let tokens = []; - while (expr.length > 0) { - let found = false; - for (let i = 0; i < Token.Types.length && !found; ++i) { - const t = Token.Types[i]; - const m = expr.match(t.regex); - if (m && m.length > 0) { - const len = m[0].length; - let symbol = m[1]; - let value; - switch (t.type) { - case Token.Type.Literal: - try { - if (typeof JSBI !== 'function') { - value = BigInt(Token.BasePrefix[t.base] + symbol); - } - else { - value = JSBI.BigInt(Token.BasePrefix[t.base] + symbol); - } - } - catch (error) { - return { error }; - } - break; - default: - if (symbol === '-' - && - ( - tokens.length === 0 || - tokens.last.type === Token.Type.LeftParenthesis || - Token.Operators.indexOf(tokens.last.value) >= 0 - ) - ) { - symbol = Token.Symbols.UnaryMinus; - } - value = symbol; - break; - } - tokens.push(new Token(t.type, value)); - expr = expr.substring(len); - found = true; - } - } - if (!found) { - return { error: `invalid expression: ${expr}` }; - } - } - return { tokens }; -} - -// The Shunting-Yard algorithm takes a list of tokens and rearranges them -// into an array with the tokens in Reverse Polish Notation. -let shuntingYard = tokens => { - const ops = new Stack(); - const queue = new Stack(); - for (const token of tokens) { - switch (token.type) { - case Token.Type.Literal: - // fall-through - case Token.Type.Variable: - queue.push(token); - break; - case Token.Type.Function: - // TODO - case Token.Type.Operator: - while (ops.top && - (ops.top.type !== Token.Type.LeftParenthesis) - && - ( - (ops.top.precedence > token.precedence) - || - (ops.top.precedence === token.precedence && ops.top.associativity === LEFT) - ) - ) { - queue.push(ops.pop()); - } - if (token.value !== ',') { - ops.push(token); - } - break; - case Token.Type.LeftParenthesis: - ops.push(token); - break; - case Token.Type.RightParenthesis: - while (ops.top && ops.top.type !== Token.Type.LeftParenthesis) { - queue.push(ops.pop()); - } - if (ops.top.type === Token.Type.LeftParenthesis) { - ops.pop(); - } - break; - default: - break; - } - } - while (ops.length > 0) { - queue.push(ops.pop()); - } - return queue; -} diff --git a/static/fonts/OTF/VictorMono-Bold.otf b/static/fonts/OTF/VictorMono-Bold.otf new file mode 100755 index 0000000..f72e15d Binary files /dev/null and b/static/fonts/OTF/VictorMono-Bold.otf differ diff --git a/static/fonts/OTF/VictorMono-BoldItalic.otf b/static/fonts/OTF/VictorMono-BoldItalic.otf new file mode 100755 index 0000000..052dfd7 Binary files /dev/null and b/static/fonts/OTF/VictorMono-BoldItalic.otf differ diff --git a/static/fonts/OTF/VictorMono-BoldOblique.otf b/static/fonts/OTF/VictorMono-BoldOblique.otf new file mode 100755 index 0000000..47f0a42 Binary files /dev/null and b/static/fonts/OTF/VictorMono-BoldOblique.otf differ diff --git a/static/fonts/OTF/VictorMono-ExtraLight.otf b/static/fonts/OTF/VictorMono-ExtraLight.otf new file mode 100755 index 0000000..f22bb92 Binary files /dev/null and b/static/fonts/OTF/VictorMono-ExtraLight.otf differ diff --git a/static/fonts/OTF/VictorMono-ExtraLightItalic.otf b/static/fonts/OTF/VictorMono-ExtraLightItalic.otf new file mode 100755 index 0000000..bdd6cff Binary files /dev/null and b/static/fonts/OTF/VictorMono-ExtraLightItalic.otf differ diff --git a/static/fonts/OTF/VictorMono-ExtraLightOblique.otf b/static/fonts/OTF/VictorMono-ExtraLightOblique.otf new file mode 100755 index 0000000..548ee1f Binary files /dev/null and b/static/fonts/OTF/VictorMono-ExtraLightOblique.otf differ diff --git a/static/fonts/OTF/VictorMono-Italic.otf b/static/fonts/OTF/VictorMono-Italic.otf new file mode 100755 index 0000000..11afceb Binary files /dev/null and b/static/fonts/OTF/VictorMono-Italic.otf differ diff --git a/static/fonts/OTF/VictorMono-Light.otf b/static/fonts/OTF/VictorMono-Light.otf new file mode 100755 index 0000000..90f18a3 Binary files /dev/null and b/static/fonts/OTF/VictorMono-Light.otf differ diff --git a/static/fonts/OTF/VictorMono-LightItalic.otf b/static/fonts/OTF/VictorMono-LightItalic.otf new file mode 100755 index 0000000..15d266b Binary files /dev/null and b/static/fonts/OTF/VictorMono-LightItalic.otf differ diff --git a/static/fonts/OTF/VictorMono-LightOblique.otf b/static/fonts/OTF/VictorMono-LightOblique.otf new file mode 100755 index 0000000..0de22c8 Binary files /dev/null and b/static/fonts/OTF/VictorMono-LightOblique.otf differ diff --git a/static/fonts/OTF/VictorMono-Medium.otf b/static/fonts/OTF/VictorMono-Medium.otf new file mode 100755 index 0000000..39a24fe Binary files /dev/null and b/static/fonts/OTF/VictorMono-Medium.otf differ diff --git a/static/fonts/OTF/VictorMono-MediumItalic.otf b/static/fonts/OTF/VictorMono-MediumItalic.otf new file mode 100755 index 0000000..c9789c0 Binary files /dev/null and b/static/fonts/OTF/VictorMono-MediumItalic.otf differ diff --git a/static/fonts/OTF/VictorMono-MediumOblique.otf b/static/fonts/OTF/VictorMono-MediumOblique.otf new file mode 100755 index 0000000..bbdcdb7 Binary files /dev/null and b/static/fonts/OTF/VictorMono-MediumOblique.otf differ diff --git a/static/fonts/OTF/VictorMono-Oblique.otf b/static/fonts/OTF/VictorMono-Oblique.otf new file mode 100755 index 0000000..9e47e56 Binary files /dev/null and b/static/fonts/OTF/VictorMono-Oblique.otf differ diff --git a/static/fonts/OTF/VictorMono-Regular.otf b/static/fonts/OTF/VictorMono-Regular.otf new file mode 100755 index 0000000..c7bd290 Binary files /dev/null and b/static/fonts/OTF/VictorMono-Regular.otf differ diff --git a/static/fonts/OTF/VictorMono-SemiBold.otf b/static/fonts/OTF/VictorMono-SemiBold.otf new file mode 100755 index 0000000..8b033b4 Binary files /dev/null and b/static/fonts/OTF/VictorMono-SemiBold.otf differ diff --git a/static/fonts/OTF/VictorMono-SemiBoldItalic.otf b/static/fonts/OTF/VictorMono-SemiBoldItalic.otf new file mode 100755 index 0000000..cdfe9e6 Binary files /dev/null and b/static/fonts/OTF/VictorMono-SemiBoldItalic.otf differ diff --git a/static/fonts/OTF/VictorMono-SemiBoldOblique.otf b/static/fonts/OTF/VictorMono-SemiBoldOblique.otf new file mode 100755 index 0000000..50452c5 Binary files /dev/null and b/static/fonts/OTF/VictorMono-SemiBoldOblique.otf differ diff --git a/static/fonts/OTF/VictorMono-Thin.otf b/static/fonts/OTF/VictorMono-Thin.otf new file mode 100755 index 0000000..4e25778 Binary files /dev/null and b/static/fonts/OTF/VictorMono-Thin.otf differ diff --git a/static/fonts/OTF/VictorMono-ThinItalic.otf b/static/fonts/OTF/VictorMono-ThinItalic.otf new file mode 100755 index 0000000..4ae05a3 Binary files /dev/null and b/static/fonts/OTF/VictorMono-ThinItalic.otf differ diff --git a/static/fonts/OTF/VictorMono-ThinOblique.otf b/static/fonts/OTF/VictorMono-ThinOblique.otf new file mode 100755 index 0000000..4d3d77b Binary files /dev/null and b/static/fonts/OTF/VictorMono-ThinOblique.otf differ diff --git a/static/fonts/WOFF2/VictorMono-Bold.woff2 b/static/fonts/WOFF2/VictorMono-Bold.woff2 new file mode 100755 index 0000000..7249c2c Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Bold.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-BoldItalic.woff2 b/static/fonts/WOFF2/VictorMono-BoldItalic.woff2 new file mode 100755 index 0000000..0fa7a11 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-BoldItalic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-BoldOblique.woff2 b/static/fonts/WOFF2/VictorMono-BoldOblique.woff2 new file mode 100755 index 0000000..827c36c Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-BoldOblique.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-ExtraLight.woff2 b/static/fonts/WOFF2/VictorMono-ExtraLight.woff2 new file mode 100755 index 0000000..a2ac7ea Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-ExtraLight.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-ExtraLightItalic.woff2 b/static/fonts/WOFF2/VictorMono-ExtraLightItalic.woff2 new file mode 100755 index 0000000..4791649 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-ExtraLightItalic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-ExtraLightOblique.woff2 b/static/fonts/WOFF2/VictorMono-ExtraLightOblique.woff2 new file mode 100755 index 0000000..814cb1d Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-ExtraLightOblique.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-Italic.woff2 b/static/fonts/WOFF2/VictorMono-Italic.woff2 new file mode 100755 index 0000000..b4aa39a Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Italic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-Light.woff2 b/static/fonts/WOFF2/VictorMono-Light.woff2 new file mode 100755 index 0000000..b059857 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Light.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-LightItalic.woff2 b/static/fonts/WOFF2/VictorMono-LightItalic.woff2 new file mode 100755 index 0000000..5101bd2 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-LightItalic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-LightOblique.woff2 b/static/fonts/WOFF2/VictorMono-LightOblique.woff2 new file mode 100755 index 0000000..98eb08c Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-LightOblique.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-Medium.woff2 b/static/fonts/WOFF2/VictorMono-Medium.woff2 new file mode 100755 index 0000000..e712bc0 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Medium.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-MediumItalic.woff2 b/static/fonts/WOFF2/VictorMono-MediumItalic.woff2 new file mode 100755 index 0000000..7a489d7 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-MediumItalic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-MediumOblique.woff2 b/static/fonts/WOFF2/VictorMono-MediumOblique.woff2 new file mode 100755 index 0000000..98797cb Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-MediumOblique.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-Oblique.woff2 b/static/fonts/WOFF2/VictorMono-Oblique.woff2 new file mode 100755 index 0000000..caef3a8 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Oblique.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-Regular.woff2 b/static/fonts/WOFF2/VictorMono-Regular.woff2 new file mode 100755 index 0000000..63af0d4 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Regular.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-SemiBold.woff2 b/static/fonts/WOFF2/VictorMono-SemiBold.woff2 new file mode 100755 index 0000000..d520338 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-SemiBold.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-SemiBoldItalic.woff2 b/static/fonts/WOFF2/VictorMono-SemiBoldItalic.woff2 new file mode 100755 index 0000000..3a8970e Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-SemiBoldItalic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-SemiBoldOblique.woff2 b/static/fonts/WOFF2/VictorMono-SemiBoldOblique.woff2 new file mode 100755 index 0000000..8409aa1 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-SemiBoldOblique.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-Thin.woff2 b/static/fonts/WOFF2/VictorMono-Thin.woff2 new file mode 100755 index 0000000..58c3309 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-Thin.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-ThinItalic.woff2 b/static/fonts/WOFF2/VictorMono-ThinItalic.woff2 new file mode 100755 index 0000000..c1ab8fd Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-ThinItalic.woff2 differ diff --git a/static/fonts/WOFF2/VictorMono-ThinOblique.woff2 b/static/fonts/WOFF2/VictorMono-ThinOblique.woff2 new file mode 100755 index 0000000..39fb9d3 Binary files /dev/null and b/static/fonts/WOFF2/VictorMono-ThinOblique.woff2 differ diff --git a/tests/.gitignore b/tests/.gitignore index c2658d7..504afef 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,2 @@ node_modules/ +package-lock.json diff --git a/tests/package.json b/tests/package.json index 828e83f..9be69cb 100644 --- a/tests/package.json +++ b/tests/package.json @@ -24,6 +24,6 @@ }, "homepage": "https://github.com/607011/bincalc", "devDependencies": { - "mocha": "^6.1.4" + "mocha": "^6.2.3" } } diff --git a/tests/test.js b/tests/test.js index 2359461..b9cf00a 100644 --- a/tests/test.js +++ b/tests/test.js @@ -17,7 +17,7 @@ const assert = require('assert') const fs = require('fs') const vm = require('vm') -vm.runInThisContext(fs.readFileSync('../shunting-yard.js')) +vm.runInThisContext(fs.readFileSync('../numbercruncher.js')) it('testing Stack', () => { let stack = new Stack() @@ -29,35 +29,36 @@ it('testing Stack', () => { }) it('testing tokenizer', () => { - const tokenized = tokenize('(1+2)*3**4-5') + const tokenized = ShuntingYard.tokenize('(1+2)*3**4-5') const expectedTokens = [ - new Token(5, '('), - new Token(1, 1n), - new Token(2, '+'), - new Token(1, 2n), - new Token(6, ')'), - new Token(2, '*'), - new Token(1, 3n), - new Token(2, '**'), - new Token(1, 4n), - new Token(2, '-'), - new Token(1, 5n) + new Token(Token.Type.LeftParenthesis, '('), + new Token(Token.Type.Literal, 1n), + new Token(Token.Type.Operator, '+'), + new Token(Token.Type.Literal, 2n), + new Token(Token.Type.RightParenthesis, ')'), + new Token(Token.Type.Operator, '*'), + new Token(Token.Type.Literal, 3n), + new Token(Token.Type.Operator, '**'), + new Token(Token.Type.Literal, 4n), + new Token(Token.Type.Operator, '-'), + new Token(Token.Type.Literal, 5n) ] assert.ok(tokenized.tokens.every((element, idx) => element.equals(expectedTokens[idx]))) }) it('testing shunting-yard', () => { - const tokenized = tokenize('(1+2)*3**4-5') + const tokenized = ShuntingYard.tokenize('(1+2)*3**4-5') const expectedRPN = [ - new Token(1, 1n), - new Token(1, 2n), - new Token(2, '+'), - new Token(1, 3n), - new Token(1, 4n), - new Token(2, '**'), - new Token(2, '*'), - new Token(1, 5n), - new Token(2, '-') + new Token(Token.Type.Literal, 1n), + new Token(Token.Type.Literal, 2n), + new Token(Token.Type.Operator, '+'), + new Token(Token.Type.Literal, 3n), + new Token(Token.Type.Literal, 4n), + new Token(Token.Type.Operator, '**'), + new Token(Token.Type.Operator, '*'), + new Token(Token.Type.Literal, 5n), + new Token(Token.Type.Operator, '-') ] - assert.ok(shuntingYard(tokenized.tokens).stack.every((element, idx) => element.equals(expectedRPN[idx]))) + const rpnTokens = ShuntingYard.shunt(tokenized.tokens); + assert.ok(rpnTokens.values().every((element, idx) => element.equals(expectedRPN[idx]))) }) diff --git a/worker-message-handler.js b/worker-message-handler.js index 29d3c70..8ea0236 100644 --- a/worker-message-handler.js +++ b/worker-message-handler.js @@ -1,4 +1,4 @@ -// Copyright (c) 2019 Oliver Lau , Heise Medien GmbH & Co. KG +// Copyright (c) 2019-2024 Oliver Lau , Heise Medien GmbH & Co. KG // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,38 +16,38 @@ 'use strict'; onmessage = event => { - let dtCalc = 0; - let dtRender = 0; - const expressions = event.data.expressions; - const base = event.data.base; - let errorFound = false; - let results = []; - const calculator = new Calculator(); - for (const expression of expressions) { - const calcT0 = Date.now(); - const expr = expression.replace(/\s+/g, ''); - const { result, error } = calculator.calculate(expr); - dtCalc += Date.now() - calcT0; - if (error) { - errorFound = true; - postMessage({ error: error.message || error }); - break; + let dtCalc = 0; + let dtRender = 0; + const expressions = event.data.expressions; + const base = event.data.base; + let errorFound = false; + let results = []; + const calculator = new Calculator(); + for (const expression of expressions) { + const calcT0 = Date.now(); + const expr = expression.replace(/\s+/g, ''); + const { result, error } = calculator.calculate(expr); + dtCalc += Date.now() - calcT0; + if (error) { + errorFound = true; + postMessage({ error: error.message || error }); + break; + } + if (typeof result === 'bigint' || result instanceof Array) { + const renderT0 = Date.now(); + results.push({ + expression: expr, + result: result.toString(base), + }); + dtRender += Date.now() - renderT0; + } } - if (typeof result === 'bigint' || result instanceof Array) { - const renderT0 = Date.now(); - results.push({ - expression: expr, - result: result.toString(base), - }); - dtRender += Date.now() - renderT0; + if (!errorFound) { + postMessage({ + results: results, + variables: calculator.variables, + dtCalc: dtCalc, + dtRender: dtRender, + }); } - } - if (!errorFound) { - postMessage({ - results: results, - variables: calculator.variables, - dtCalc: dtCalc, - dtRender: dtRender, - }); - } }