├── .gitignore ├── README.md ├── evaluate ├── LICENCE ├── README.md ├── index.js ├── package.json └── test │ ├── eval.js │ └── test.js ├── index.js ├── package.json ├── serialize ├── LICENCE ├── README.md ├── index.js ├── package.json └── test │ ├── random.js │ └── test.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [decimal.js](https://github.com/MikeMcl/decimal.js/) extensions 2 | 3 | - [serialize](https://github.com/MikeMcl/decimal.js-extensions/tree/master/serialize) 4 | - [evaluate](https://github.com/MikeMcl/decimal.js-extensions/tree/master/evaluate) 5 | -------------------------------------------------------------------------------- /evaluate/LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Mclaughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /evaluate/README.md: -------------------------------------------------------------------------------- 1 | A [decimal.js](https://github.com/MikeMcl/decimal.js/) extension to 2 | 3 | # evaluate arithmetic expressions 4 | 5 | This is a lightweight expression parser. See [math.js](https://mathjs.org/) for a much more powerful and full-featured expression parser and math library. 6 | 7 |
8 | 9 | ## Usage example 10 | 11 | ```javascript 12 | import Decimal from 'decimal.js'; 13 | import { evaluate } from 'decimal.js-extensions-evaluate'; 14 | 15 | evaluate.extend(Decimal); 16 | 17 | let result = Decimal.evaluate('0.1 + 0.2'); 18 | 19 | console.log(result); // '0.3' 20 | console.log(result instanceof Decimal); // true 21 | 22 | Decimal.precision = 30; 23 | 24 | result = Decimal.eval('2sin(x)', { x: 3, sin: n => n.sin() }); 25 | 26 | console.log(result); // '0.282240016119734444201489605616' 27 | console.log(Decimal.eval.expression); // '2*sin(x)' 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | $ npm test 34 | 35 | $ node test/eval "cos(1 + sin(0.5))" 36 | ``` 37 | 38 | ## Licence 39 | 40 | [MIT](https://github.com/MikeMcl/decimal.js-extensions/blob/master/LICENCE) 41 | 42 | ## Credits 43 | 44 | The implementation is derived from the article *Top Down Operator Precedence* 45 | by Douglas Crockford, available at 46 | 47 | . 48 | 49 | Also see: 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | ## Scope example 58 | 59 | ```javascript 60 | const scope = { 61 | NaN, 62 | Infinity, 63 | // Constants to 40 d.p. 64 | E: '2.7182818284590452353602874713526624977572', 65 | LN2: '0.6931471805599453094172321214581765680755', 66 | LN10: '2.3025850929940456840179914546843642076011', 67 | LOG2E: '1.4426950408889634073599246810018921374266', 68 | LOG10E: '0.4342944819032518276511289189166050822944', 69 | PI: '3.1415926535897932384626433832795028841972', 70 | SQRT1_2: '0.7071067811865475244008443621048490392848', 71 | SQRT2: '1.4142135623730950488016887242096980785697', 72 | PHI: '1.6180339887498948482045868343656381177203', 73 | abs: x => x.abs(), 74 | acos: x => x.acos(), 75 | acosh: x => x.acosh(), 76 | asin: x => x.asin(), 77 | asinh: x => x.asinh(), 78 | atan: x => x.atan(), 79 | atanh: x => x.atanh(), 80 | atan2: (y, x) => Decimal.atan2(y, x), 81 | cbrt: x => x.cbrt(), 82 | ceil: x => x.ceil(), 83 | cos: x => x.cos(), 84 | cosh: x => x.cosh(), 85 | exp: x => x.exp(), 86 | floor: x => x.floor(), 87 | ln: x => x.ln(), 88 | log: (x, y) => x.log(y), 89 | log10: x => Decimal.log10(x), 90 | log2: x => Decimal.log2(x), 91 | max: (...args) => Decimal.max(...args), 92 | min: (...args) => Decimal.min(...args), 93 | random: (n) => Decimal.random(+n), 94 | round: x => x.round(), 95 | sign: x => Decimal.sign(x), 96 | sin: x => x.sin(), 97 | sinh: x => x.sinh(), 98 | sqrt: x => x.sqrt(), 99 | tan: x => x.tan(), 100 | tanh: x => x.tanh(), 101 | trunc: x => x.trunc(), 102 | }; 103 | 104 | result = Decimal.eval('3.4564564645E + 4(PHI - cos(LN2) + exp(PI)) / 2.003e+12', scope); 105 | 106 | console.log(result); // '9.39562279835805409202716773322' 107 | console.log(Decimal.eval.expression); // '3.4564564645*E+4*(PHI-cos(LN2)+exp(PI))/2.003e+12' 108 | ``` 109 | 110 | ## Notes 111 | 112 | The results of operations are calculated to the number of significant digits specified by the value of `Decimal.precision` and rounded using the rounding mode of `Decimal.rounding`. 113 | 114 | ```js 115 | Decimal.precision = 5; 116 | Decimal.eval('1/3'); // '0.33333' 117 | ``` 118 | 119 | The comparison operators return `1` for `true` and `0` for `false`. 120 | 121 | ```js 122 | Decimal.eval('2 > 3'); // '0' 123 | ``` 124 | 125 | As with JavaScript, the logical operators `&&` and `||` return one of their operands. 126 | 127 | ```js 128 | Decimal.eval('2 && 3'); // '3' 129 | ``` 130 | 131 | Exponential notation is supported. 132 | 133 | ```js 134 | Decimal.eval('1.234e+5 == 123400'); // '1' 135 | ``` 136 | 137 | As shown above, user-defined variables and functions are supported through a *scope* object passed as the second argument. 138 | 139 | ```js 140 | Decimal.eval('3sin(x)', { x: 3, sin: n => n.sin() }); // '0.4233600241796016663' 141 | ``` 142 | 143 | Once a *scope* is defined, it exists until it is replaced by a new *scope*. 144 | 145 | ```js 146 | Decimal.eval('x + y', { x: 0.1, y: 0.2 }); // '0.3' 147 | Decimal.eval('2(x - 3y)'); // '1' 148 | Decimal.eval('2xy'); // '0.04' 149 | Decimal.eval('xy', {}); // 'Decimal.eval: unknown symbol: x' 150 | ``` 151 | 152 | Multiplication is implicit for immediately adjacent values, but note that multiplication and division are left-associative: 153 | 154 | ```js 155 | Decimal.eval('1/2x', { x: 4 }); // '2' (1/2)*x 156 | Decimal.eval('1/(2x)'); // '0.125' 1/(2*x) 157 | ``` 158 | 159 | The evaluated expression is available at `Decimal.eval.expression`. 160 | 161 | ```js 162 | Decimal.eval('2x(3x + 4y)'); 163 | Decimal.eval.expression; // '2*x*(3*x + 4*y)' 164 | ``` 165 | 166 | Each variable's value and each function's return value is passed to the `Decimal` constructor, so a *number*, *string* or *Decimal* can be used in the definition in the scope object. 167 | 168 | ```js 169 | Decimal.eval('x % y + z', { x: 4, y: '3', z: new Decimal('2') }); // '3' 170 | ``` 171 | 172 | The values that are passed to functions are `Decimal` values, so their prototype methods are available. 173 | 174 | ```js 175 | Decimal.eval('add(0.1, 0.2)', { add: (x, y) => x.plus(y) }); // '0.3' 176 | ``` 177 | 178 | The definition of a valid identifier for a variable or function is the same as for JavaScript except that the only non-ASCII characters allowed are the letters of the Unicode *Greek and Coptic* range: `\u0370-\u03FF`. 179 | I.e. an identifier must start with a letter, `_` or `$`, and any further characters must be letters, numbers, `_` or `$`. 180 | 181 | ```js 182 | Decimal.eval('2πr', { π: Math.PI, r: 1 }); // '6.283185307179586' 183 | ``` 184 | 185 | A variable or function's value can be changed without having to redefine the scope, by passing an object containing the updated definitions as the first and only argument. 186 | 187 | Below, the value of `π` is updated and the last expression `'2πr'` is re-evaluated each time. 188 | 189 | ```js 190 | Decimal.eval({ π: '3.14159265358979323846' }); // '6.28318530717958647692' 191 | Decimal.eval({ π: 3 }); // '6' 192 | ``` 193 | 194 | A new scope has not been created so `r` is still available. 195 | 196 | ```js 197 | Decimal.eval('r'); // 1 198 | Decimal.eval('r', {}); // 'Decimal.eval: unknown symbol: r' 199 | ``` 200 | 201 | When a new expression is passed to `Decimal.eval` it is tokenized and those tokens are re-evaluated on each subsequent `eval` call until a new expression is passed and a new set of tokens is created. 202 | 203 | ```js 204 | Decimal.eval('x^y', { x: 2, y: 3 }); // '8' 2^3 205 | Decimal.eval({ y: -3 }); // '0.125' 2^-3 206 | Decimal.eval({ x: 4 }); // '0.015625' 4^-3 207 | 208 | Decimal.eval('1'); // '1' 209 | Decimal.eval({ y: 4 }); // '1' 210 | Decimal.eval('x^y'); // '256' 4^4 211 | ``` 212 | 213 | Note, though, that *new* variables or functions cannot be added via the updating object. 214 | 215 | ```js 216 | Decimal.eval({ z: 5 }); // 'Decimal.eval: identifier not in scope: z' 217 | ``` 218 | 219 | --- -------------------------------------------------------------------------------- /evaluate/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A decimal.js extension to evaluate arithmetic expressions. 3 | * https://github.com/MikeMcl/decimal.js-extensions 4 | * Copyright (c) 2020 Michael Mclaughlin. 5 | * MIT License https://opensource.org/licenses/MIT 6 | * 7 | * Decimal.eval( [expression [, scope]] ) 8 | * 9 | * Return a new Decimal whose value is the result of evaluating a string expression in the context 10 | * of a scope object. 11 | * 12 | * [expression] {string} An arithmetic/boolean expression. 13 | * [scope] {object} An object defining variables and/or functions. 14 | * 15 | * Examples: 16 | * 17 | * Decimal.eval('0.1 + 0.2').toString() // '0.3' 18 | * Decimal.eval('xyz', { x:'9', y:'-4', z:'7' }); // '-252' 19 | * 20 | * Operator precedence: 21 | * ( ) 22 | * ^ power √ (\u221A) square root (right-associative) 23 | * ! + - (unary) 24 | * * / % 25 | * + - 26 | * < > <= >= 27 | * == != 28 | * && 29 | * || 30 | * 31 | * The comparison operators return 1 for true and 0 for false. 32 | * The logical operators return one of their operands, e.g. 1 && 2 returns 2. 33 | * 34 | * Implicit multiplication is supported, e.g. with scope `{ x:1, y:2 }`, 35 | * `2x(3x + 4y)` is evaluated as `2*x*(3*x + 4*y)`. 36 | * 37 | * The previously evaluated expression, with any added '*', is available at Decimal.eval.expression. 38 | * 39 | * To change the value of a variable or function without creating a new scope and without 40 | * re-tokenizing an expression, pass an object with the re-definitions as the sole argument. 41 | * 42 | * Decimal.eval('x^y', {x:2, y:3}) // '8' 43 | * Decimal.eval({y:-3}) // '0.125' 44 | * 45 | * To add support for NaN, Infinity and, for example, the sine function use, for example: 46 | * 47 | * Decimal.eval('3sin(e)', { 48 | * e: '2.71828182845904523536', 49 | * sin: x => x.sin(), 50 | * NaN: NaN, 51 | * Infinity: Infinity 52 | * }); 53 | */ 54 | 55 | function evaluator(Decimal) { 56 | const evalError = 'Decimal.eval:'; 57 | const longestFirst = (a, b) => b.length - a.length; 58 | const regExpNum = '(?:(\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)|('; 59 | const regExpSymbol = ')|(\\)))(?=([\\w($\\u221A\\u0370-\\u03FF]|!(?!=))?)|([!<>=]=|[-+*\\/(^%> { 69 | const ops = {}; 70 | 71 | // lbp: left binding power. 72 | const add = (op, lbp, fn, prefix) => { 73 | const obj = {}; 74 | 75 | obj.lbp = lbp; 76 | if (lbp) { 77 | obj.infix = typeof fn == 'string' 78 | ? lbp < 50 // comparison methods returning true/false 79 | ? left => new Decimal(Number(left[fn](evaluate(lbp)))) 80 | : left => left[fn](evaluate(lbp)) 81 | : fn; 82 | 83 | if (prefix) obj.prefix = prefix; 84 | } else { 85 | obj.prefix = fn; 86 | } 87 | 88 | obj.val = op; 89 | ops[op] = obj; 90 | }; 91 | 92 | add('^', 80, left => left.pow(evaluate(79))); 93 | add('*', 60, 'times'); 94 | add('/', 60, 'div'); 95 | add('%', 60, 'mod'); 96 | add('+', 50, 'plus', () => evaluate(70)); 97 | add('-', 50, 'minus', () => { 98 | const r = new Decimal(evaluate(70)); 99 | r.s = -r.s; 100 | return r; 101 | }); 102 | add('>', 40, 'gt'); 103 | add('>=', 40, 'gte'); 104 | add('<', 40, 'lt'); 105 | add('<=', 40, 'lte'); 106 | add('==', 30, 'eq'); 107 | add('!=', 30, left => new Decimal(Number(!left.eq(evaluate(30))))); 108 | add('&&', 20, left => { 109 | const r = evaluate(20); 110 | return left.isZero() ? left : r; 111 | }); 112 | add('||', 10, left => { 113 | const r = evaluate(10); 114 | return left.isZero() ? r : left; 115 | }); 116 | add('\u221A', 0, () => evaluate(79).sqrt()); 117 | 118 | // If 'left' is 0 return 1 else return 0. 119 | add('!', 0, () => new Decimal(Number(evaluate(70).isZero()))); 120 | add('(', 0, function () { 121 | const r = evaluate(0); 122 | if (token.val !== ')') throw Error(`${evalError} expected )`); 123 | token = TOKENS[++index]; 124 | return r; 125 | }); 126 | add(')', 0); 127 | add(',', 0); 128 | 129 | return ops; 130 | })(); 131 | 132 | const funcPrefix = function () { 133 | const args = []; 134 | 135 | if (token.val !== '(') throw SyntaxError(`${evalError} expected (`); 136 | 137 | token = TOKENS[++index]; 138 | 139 | if (token.val !== ')') { 140 | while (true) { 141 | args.push(evaluate(0)); 142 | if (token.val !== ',') { 143 | if (token.val !== ')') throw SyntaxError(`${evalError} expected )`); 144 | break; 145 | } 146 | 147 | token = TOKENS[++index]; 148 | } 149 | } 150 | 151 | token = TOKENS[++index]; 152 | 153 | return new Decimal(SCOPE[this.val].apply(null, args)); 154 | }; 155 | 156 | const varPrefix = function () { 157 | return SCOPE[this.val]; 158 | }; 159 | 160 | const numPrefix = function () { 161 | return this.val; 162 | }; 163 | 164 | const evaluate = rbp => { 165 | let left; 166 | let t = token; 167 | 168 | if (!t.prefix) { 169 | throw SyntaxError(`${evalError} unexpected ${t.val === 'end' ? 'end' : 'symbol: ' + t.val}`); 170 | } 171 | 172 | token = TOKENS[++index]; 173 | left = t.prefix(); 174 | 175 | while (rbp < token.lbp) { 176 | t = token; 177 | token = TOKENS[++index]; 178 | left = t.infix(left); 179 | } 180 | 181 | // 'left' is returned to 'infix' or 'prefix', or it may be the final result. 182 | return left; 183 | }; 184 | 185 | return (expression, scope) => { 186 | if (typeof expression === 'string' && expression.trim() !== '') { 187 | 188 | // Create new SCOPE. 189 | if (scope !== undefined) { 190 | if (scope !== null && typeof scope === 'object') { 191 | SCOPE = {}; 192 | 193 | for (const [id, val] of Object.entries(scope)) { 194 | 195 | // Allow characters in Unicode 'Greek and Coptic' range: \u0370-\u03FF 196 | // VALID_IDENTIFIER: /^[a-zA-Z\u0370-\u03FF_$][\w\u0370-\u03FF$]*$/ 197 | if (!VALID_IDENTIFIER.test(id)) { 198 | throw SyntaxError(`${evalError} invalid identifier: ${id}`); 199 | } 200 | SCOPE[id] = typeof val === 'function' ? val : new Decimal(val); 201 | } 202 | 203 | const identifiers = Object.keys(SCOPE); 204 | if (identifiers.length > 0) { 205 | identifiers.sort(longestFirst); 206 | TOKENIZER = new RegExp(`${regExpNum}${identifiers.join('|').replace(/\$/g, '\\$')}${regExpSymbol}`, 'g'); 207 | } else { 208 | TOKENIZER = DEFAULT_TOKENIZER; 209 | } 210 | } else { 211 | throw TypeError(`${evalError} invalid scope: ${scope}`); 212 | } 213 | } 214 | 215 | // Uncomment to support the use of these other multiplication and division symbols: 216 | // × multiplication \xd7 (215) 217 | // ÷ division \xf7 (247) 218 | // expression = expression.replace(/\xd7/g, '*').replace(/\xf7/g, '/'); 219 | 220 | // Uncomment to support the use of '**' for the power operation. 221 | expression = expression.replace(/\*\*/g, '^'); 222 | 223 | // Example TOKENIZER with capture groups numbered (and a scope with a variable 'x' and a function 'y'): 224 | // 1 2 3 4 5 225 | // (?:(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(x|y)|(\)))(?=([\w($\u221A\u0370-\u03FF]|!(?!=))?)|([!<>=]=|[-+*\/(^%> x.abs(), 36 | acos: x => x.acos(), 37 | acosh: x => x.acosh(), 38 | asin: x => x.asin(), 39 | asinh: x => x.asinh(), 40 | atan: x => x.atan(), 41 | atanh: x => x.atanh(), 42 | atan2: (y, x) => Decimal.atan2(y, x), 43 | cbrt: x => x.cbrt(), 44 | ceil: x => x.ceil(), 45 | cos: x => x.cos(), 46 | cosh: x => x.cosh(), 47 | exp: x => x.exp(), 48 | floor: x => x.floor(), 49 | ln: x => x.ln(), 50 | log: (x, y) => x.log(y), 51 | log10: x => Decimal.log10(x), 52 | log2: x => Decimal.log2(x), 53 | max: (...args) => Decimal.max(...args), 54 | min: (...args) => Decimal.min(...args), 55 | random: (n) => Decimal.random(+n), 56 | round: x => x.round(), 57 | sign: x => Decimal.sign(x), 58 | sin: x => x.sin(), 59 | sinh: x => x.sinh(), 60 | sqrt: x => x.sqrt(), 61 | tan: x => x.tan(), 62 | tanh: x => x.tanh(), 63 | trunc: x => x.trunc(), 64 | }; 65 | } 66 | 67 | Decimal.precision = 20; 68 | const start = performance.now(); 69 | const result = Decimal.eval(expression, scope); 70 | const end = performance.now(); 71 | log(`${Decimal.eval.expression} = ${result}`); 72 | log(`Time taken: ${((end - start) / 1e3).toFixed(3)} secs.`); 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { serialize } from './serialize/index.js'; 2 | export { evaluate } from './evaluate/index.js'; 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "decimal.js-extensions", 3 | "version": "1.0.0", 4 | "description": "decimal.js extensions", 5 | "main": "index.js", 6 | "files": ["index.js"], 7 | "scripts": { 8 | "test": "node test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/MikeMcl/decimal.js-extensions.git" 13 | }, 14 | "devDependencies": { 15 | "decimal.js": "latest" 16 | }, 17 | "keywords": [ 18 | "decimal.js", 19 | "decimal", 20 | "bignumber", 21 | "serialize", 22 | "evaluate" 23 | ], 24 | "author": "Michael Mclaughlin", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/MikeMcl/decimal.js-extensions/issues" 28 | }, 29 | "homepage": "https://github.com/MikeMcl/decimal.js-extensions/blob/master/README.md", 30 | "type": "module" 31 | } 32 | -------------------------------------------------------------------------------- /serialize/LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Mclaughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /serialize/README.md: -------------------------------------------------------------------------------- 1 | 2 | A [decimal.js](https://github.com/MikeMcl/decimal.js/) extension for 3 | 4 | # Decimal to ArrayBuffer serialization 5 | 6 | ## Usage example 7 | 8 | ```javascript 9 | import Decimal from 'decimal.js'; 10 | import { serialize } from 'decimal.js-extensions-serialize'; 11 | 12 | serialize.extend(Decimal); 13 | 14 | const x = new Decimal('1234567899.54645456456456546213253466'); 15 | const buffer = x.toArrayBuffer(); 16 | const y = Decimal.fromArrayBuffer(buffer); 17 | 18 | console.log(x.eq(y) ? 'Success' : 'Failure'); 19 | ``` 20 | 21 | ## Test 22 | 23 | ```bash 24 | $ npm test 25 | ``` 26 | 27 | ## Licence 28 | 29 | [MIT](https://github.com/MikeMcl/decimal.js-extensions/blob/master/LICENCE) 30 | -------------------------------------------------------------------------------- /serialize/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A Decimal to ArrayBuffer serialization extension for decimal.js. 3 | * 4 | * MIT Licensed 5 | * Copyright (c) 2020, Michael Mclaughlin. 6 | * 7 | * 8 | * Usage example: 9 | * 10 | * import Decimal from 'decimal.js'; 11 | * import { serialize } from 'decimal.js-extensions-serialize'; 12 | * 13 | * serialize.extend(Decimal); 14 | * 15 | * const x = new Decimal('1234567899.54645456456456546213253466'); 16 | * const buffer = x.toArrayBuffer(); 17 | * const y = Decimal.fromArrayBuffer(buffer); 18 | * 19 | * console.log(x.eq(y) ? 'Success' : 'Failure'); 20 | * 21 | */ 22 | 23 | const BASE = 1e7; 24 | const BYTES_MASK = 0b11111111; 25 | const SIX_LSB_MASK = 0b00111111; 26 | const NEG_SIGN_BIT = 0b10000000; 27 | const NEG_EXPONENT_SIGN_BIT = 0b01000000; 28 | const SMALL_INTEGER_BIT = NEG_EXPONENT_SIGN_BIT; 29 | const ALL_NINES = BASE - 1; 30 | const ALL_ZEROS = 0; 31 | const NINES_SIGNIFIER = BASE + 1; 32 | const ZEROS_SIGNIFIER = BASE; 33 | const INFINITY_BYTE = 0b01111111; 34 | const NEG_INFINITY_BYTE = 0b11111111; 35 | const NAN_BYTE = 0b01000000; 36 | const RADIX = BASE + 2; 37 | const EXPONENT_OFFSET = 7; 38 | const MAX_SMALL_EXPONENT = 30; 39 | const MAX_SMALL_INTEGER = 50; 40 | const MAX_SMALLER_INTEGER = 25; 41 | const SMALL_INTEGER_OFFSET = -25 + 37; // [26, 50] -> [38, 62] -> [26, 50] 42 | const SMALLER_INTEGER_OFFSET = 38; // [ 0, 25] -> [38, 63] -> [ 0, 25] 43 | 44 | const isArrayBuffer = buffer => buffer instanceof ArrayBuffer || 45 | Object.prototype.toString.call(buffer) === "[object ArrayBuffer]"; 46 | 47 | 48 | function toArrayBuffer() { 49 | let bytes; 50 | let firstByte; 51 | let exponent = this.e; 52 | const digits = this.d; 53 | const sign = this.s; 54 | const isSpecialValue = digits === null; 55 | 56 | if (isSpecialValue) { 57 | firstByte = isNaN(sign) ? NAN_BYTE : (sign < 0 ? NEG_INFINITY_BYTE : INFINITY_BYTE); 58 | bytes = [firstByte]; 59 | } else { 60 | const firstDigits = digits[0]; 61 | 62 | const isSmallInteger = 63 | digits.length === 1 && 64 | firstDigits <= MAX_SMALL_INTEGER && 65 | exponent === (firstDigits < 10 ? 0 : 1); 66 | 67 | if (isSmallInteger) { 68 | if (firstDigits > MAX_SMALLER_INTEGER) { 69 | firstByte = firstDigits + SMALL_INTEGER_OFFSET; 70 | firstByte |= SMALL_INTEGER_BIT; 71 | } else { 72 | firstByte = (firstDigits + SMALLER_INTEGER_OFFSET) | 0; 73 | } 74 | 75 | if (sign < 0) firstByte |= NEG_SIGN_BIT; 76 | bytes = [firstByte]; 77 | } else { 78 | firstByte = sign < 0 ? NEG_SIGN_BIT : 0; 79 | if (exponent < 0) { 80 | firstByte |= NEG_EXPONENT_SIGN_BIT; 81 | exponent = -exponent; 82 | } 83 | 84 | let exponentByteCount; 85 | if (exponent > MAX_SMALL_EXPONENT) { 86 | // `Math.floor(Math.log(0x1000000000000 - 1) / Math.log(256) + 1)` = 7 87 | exponentByteCount = 88 | exponent < 0x100 ? 1 89 | : exponent < 0x10000 ? 2 90 | : exponent < 0x1000000 ? 3 91 | : exponent < 0x100000000 ? 4 92 | : exponent < 0x10000000000 ? 5 93 | : exponent < 0x1000000000000 ? 6 94 | : 7; 95 | 96 | bytes = [firstByte | exponentByteCount]; 97 | while (exponent) { 98 | bytes.push(exponent & BYTES_MASK); 99 | exponent = Math.floor(exponent / 0x100); 100 | } 101 | } else { 102 | if (exponent !== 0) { 103 | exponent += EXPONENT_OFFSET; 104 | firstByte |= exponent; 105 | } 106 | 107 | bytes = [firstByte]; 108 | exponentByteCount = 0; 109 | } 110 | 111 | const startIndex = exponentByteCount + 1; 112 | bytes.push(0); 113 | 114 | for (let i = 0, mantissaLength = digits.length; i < mantissaLength; ) { 115 | let nextDigits = digits[i]; 116 | 117 | const zerosOrNinesRepeatMoreThanTwice = 118 | (nextDigits === ALL_ZEROS || nextDigits === ALL_NINES) && 119 | digits[i + 1] === nextDigits && 120 | digits[i + 2] === nextDigits; 121 | 122 | if (zerosOrNinesRepeatMoreThanTwice) { 123 | let repeatCount = 3; 124 | while (digits[i + repeatCount] === nextDigits) repeatCount += 1; 125 | nextDigits = nextDigits === ALL_ZEROS ? ZEROS_SIGNIFIER : NINES_SIGNIFIER; 126 | convert(nextDigits, RADIX, bytes, 0x100, startIndex); 127 | nextDigits = repeatCount; 128 | i += repeatCount; 129 | } else { 130 | i += 1; 131 | } 132 | 133 | convert(nextDigits, RADIX, bytes, 0x100, startIndex); 134 | } 135 | } 136 | } 137 | 138 | return new Uint8Array(bytes).buffer; 139 | } 140 | 141 | 142 | function fromArrayBuffer(Decimal, buffer) { 143 | let digits; 144 | let exponent; 145 | let sign; 146 | if (!isArrayBuffer(buffer)) throw Error("Not an ArrayBuffer: " + buffer); 147 | const bytes = new Uint8Array(buffer); 148 | if (!bytes.length) return null; 149 | 150 | const firstByte = bytes[0]; 151 | sign = firstByte & NEG_SIGN_BIT ? -1 : 1; 152 | const isSmallIntegerOrSpecialValue = bytes.length === 1; 153 | 154 | if (isSmallIntegerOrSpecialValue) { 155 | if (firstByte === NAN_BYTE || firstByte === INFINITY_BYTE || firstByte === NEG_INFINITY_BYTE) { 156 | digits = null; 157 | exponent = NaN; 158 | if (firstByte === NAN_BYTE) sign = NaN; 159 | } else { 160 | let integer = firstByte & SIX_LSB_MASK; 161 | if ((firstByte & SMALL_INTEGER_BIT) !== 0) { 162 | integer -= SMALL_INTEGER_OFFSET; 163 | digits = [integer]; 164 | } else { 165 | integer -= SMALLER_INTEGER_OFFSET; 166 | digits = [integer]; 167 | } 168 | exponent = integer < 10 ? 0 : 1; 169 | } 170 | } else { 171 | let indexOfLastMantissaByte = 1; 172 | exponent = firstByte & SIX_LSB_MASK; 173 | if (exponent > EXPONENT_OFFSET) { 174 | // [8, 37] => [1, 30] 175 | exponent -= EXPONENT_OFFSET; 176 | } else if (exponent !== 0) { 177 | const exponentByteCount = exponent; 178 | exponent = 0; 179 | for (let i = 0; i < exponentByteCount; ) { 180 | const leftShift = 0x100 ** i; 181 | exponent += bytes[++i] * leftShift; 182 | } 183 | 184 | indexOfLastMantissaByte += exponentByteCount; 185 | } 186 | 187 | if ((firstByte & NEG_EXPONENT_SIGN_BIT) !== 0) exponent = -exponent; 188 | 189 | const digitsInReverse = [0]; 190 | for (let i = bytes.length, startIndex = 0; i > indexOfLastMantissaByte; ) { 191 | convert(bytes[--i], 0x100, digitsInReverse, RADIX, startIndex); 192 | } 193 | 194 | digits = []; 195 | for (let i = digitsInReverse.length; i; ) { 196 | const nextDigits = digitsInReverse[--i]; 197 | if (nextDigits === ZEROS_SIGNIFIER) { 198 | for (let repeats = digitsInReverse[--i]; repeats--; digits.push(ALL_ZEROS)); 199 | } else if (nextDigits === NINES_SIGNIFIER) { 200 | for (let repeats = digitsInReverse[--i]; repeats--; digits.push(ALL_NINES)); 201 | } else { 202 | digits.push(nextDigits); 203 | } 204 | } 205 | } 206 | 207 | if (exponent > Decimal.maxE || exponent < Decimal.minE) { 208 | exponent = NaN; 209 | digits = null; 210 | } 211 | 212 | return Object.create(Decimal.prototype, { 213 | constructor: { value: Decimal }, 214 | d: { value: digits }, 215 | e: { value: exponent }, 216 | s: { value: sign }, 217 | }); 218 | } 219 | 220 | 221 | function convert(val, valBase, res, resBase, ri) { 222 | for (let i = res.length; i > ri; ) res[--i] *= valBase; 223 | res[ri] += val; 224 | for (let i = ri; i < res.length; i++) { 225 | if (res[i] > resBase - 1) { 226 | if (res[i + 1] === undefined) res[i + 1] = 0; 227 | res[i + 1] += (res[i] / resBase) | 0; 228 | res[i] %= resBase; 229 | } 230 | } 231 | } 232 | 233 | export const serialize = { 234 | extend(Decimal) { 235 | Decimal.prototype.toArrayBuffer = toArrayBuffer; 236 | Decimal.fromArrayBuffer = buffer => fromArrayBuffer(Decimal, buffer); 237 | return Decimal; 238 | } 239 | }; 240 | 241 | 242 | 243 | /* 244 | * ArrayBuffer format: 245 | * 246 | * Bit-packed first byte: 247 | * X0000000 1 bit to represent the sign 248 | * 0X000000 1 bit to represent the sign of the exponent 249 | * 00XXXXXX 6 bits (64 values) representing, depending on its value: 250 | * [0] Either an exponent magnitude of 0, or, if the exponent sign bit is set, NaN 251 | * [1, 7] The number of following bytes needed to represent the exponent magnitude 252 | * [8, 37] An exponent magnitude in the range [1, 30] 253 | * [38, 63] A Decimal value in the range [0, 25] if exponent sign bit is not set, or 254 | * [38, 62] A Decimal value in the range [26, 50] if exponent sign bit is set, and 255 | * [63] Infinity if exponent sign bit is set 256 | * 257 | * 01000000 = NaN 258 | * 01111111 = Infinity 259 | * 11111111 = -Infinity 260 | * 00100110 = 0 261 | * 10100110 = -0 262 | * 00100111 = 1 263 | * 10100111 = -1 264 | * 265 | * If a Decimal value cannot be represented by a single byte then the bytes following the first 266 | * byte represent the exponent magnitude if it is too large to be stored in the first byte, and 267 | * then the mantissa. 268 | * 269 | * Repeating zeros and nines: 270 | * If the array of base 1e7 values representing the mantissa of a Decimal value contains 271 | * elements with the value 0 (representing 0000000) or 9999999 more than twice in succession, 272 | * then these values are substituted by a signifier and a repeat count before conversion to bytes. 273 | * Examples: 274 | * [..., 0, 0, 0, ...] => [..., , ZEROS_SIGNIFIER, 3, ...] 275 | * meaning one fewer base 1e7 value has to be converted to bytes. 276 | * [..., 9999999, 9999999, 9999999, 9999999, 9999999 ...] => [..., , NINES_SIGNIFIER, 5, ...] 277 | * meaning three fewer base 1e7 values have to be converted to bytes. 278 | * [..., 0, 0, 0, 0, ..., 0, 0, 0, 0, 0, 0, 0, 0, ...] => [..., ZEROS_SIGNIFIER, 4, ..., ZEROS_SIGNIFIER, 8, ...] 279 | * meaning eight fewer base 1e7 values have to be converted to bytes. 280 | * 281 | * The maximum exponent magnitude of a Decimal value is 282 | * 0b00011111_11111001_01110011_11001010_11111010_10000000_00000000 = 9e15 283 | * so up to 7 bytes may be required to represent it. 284 | * 285 | * The highest signed 32-bit integer is 0b01111111_11111111_11111111_11111111 | 0 = 2147483647 286 | * The lowest signed 32-bit integer is 0b10000000_00000000_00000000_00000001 | 0 = -2147483647 287 | * 288 | */ 289 | -------------------------------------------------------------------------------- /serialize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "decimal.js-extensions-serialize", 3 | "version": "1.0.0", 4 | "description": "A decimal.js extension for Decimal to ArrayBuffer serialization", 5 | "main": "index.js", 6 | "files": ["index.js"], 7 | "scripts": { 8 | "test": "node test/test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/MikeMcl/decimal.js-extensions.git", 13 | "directory": "serialize" 14 | }, 15 | "devDependencies": { 16 | "decimal.js": "latest" 17 | }, 18 | "keywords": [ 19 | "decimal.js", 20 | "decimal", 21 | "bignumber" 22 | ], 23 | "author": "Michael Mclaughlin", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/MikeMcl/decimal.js-extensions/issues" 27 | }, 28 | "homepage": "https://github.com/MikeMcl/decimal.js-extensions/blob/master/serialize/README.md", 29 | "type": "module" 30 | } 31 | -------------------------------------------------------------------------------- /serialize/test/random.js: -------------------------------------------------------------------------------- 1 | const log = console.log; 2 | 3 | const randomInt = max => Math.floor(Math.random() * (max + 1)); 4 | const randomNonZeroInt = max => Math.floor(Math.random() * max) + 1; 5 | const randomIntLessThan = limit => Math.floor(Math.random() * limit); 6 | const randomIntInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 7 | const randomUInt32 = () => Math.floor(Math.random() * 0x100000000); // 2 ** 32 8 | const randomBool = () => Math.random() < 0.5; 9 | 10 | const randomDouble = (mustBeFinite = false) => { 11 | const x = new Float64Array(new Uint32Array([randomUInt32(), randomUInt32()]).buffer)[0]; 12 | return !mustBeFinite || isFinite(x) ? x : randomDouble(mustBeFinite); 13 | }; 14 | 15 | const MIN_NORMAL_POSITIVE_DOUBLE = 2.2250738585072014e-308; 16 | 17 | const randomNormalDouble = () => { 18 | let x; 19 | do x = randomDouble(true); 20 | while (Math.abs(x) < MIN_NORMAL_POSITIVE_DOUBLE); 21 | return x; 22 | }; 23 | 24 | const randomSubnormalDouble = () => { 25 | let x; 26 | do x = randomDouble(true); 27 | while (Math.abs(x) >= MIN_NORMAL_POSITIVE_DOUBLE); 28 | return x; 29 | }; 30 | 31 | const randomDoubleFromBuffer = (() => { 32 | const bufferSize = 8192; 33 | const ui32s = new Uint32Array(bufferSize * 2); 34 | const f64s = new Float64Array(ui32s.buffer); 35 | let k = bufferSize; 36 | return () => { 37 | if (k === bufferSize) { 38 | for (let i = 0, j = k * 2; i < j; ++i) ui32s[i] = randomUInt32(); 39 | k = 0; 40 | } 41 | return f64s[k++]; 42 | }; 43 | })(); 44 | 45 | const randomDoubleLimitExponent = (max = 30) => { 46 | const lo = 10 ** -max; 47 | const hi = 10 ** (max + 1); 48 | let m, x; 49 | do { 50 | x = randomDouble(true); 51 | m = Math.abs(x); 52 | } while (m >= hi || m < lo); 53 | return x; 54 | }; 55 | 56 | const randomDigits = (digitCount = 20) => { 57 | let digits = ''; 58 | do { 59 | // `1 +` to prevent exponential notation. 60 | let x = String(1 + Math.random()); 61 | // `- 1` to allow trailing zero. 62 | digits += x.slice(2, x.length - 1); 63 | } while (digits.length < digitCount); 64 | return digits.slice(0, digitCount); 65 | }; 66 | 67 | const MAXIMUM_DECIMAL_EXPONENT = 9e15; 68 | 69 | // {9, 99, 999, ..., 1e15 - 1, 9e15} 70 | // {0, 10, 100, ..., 1e14, 1e15} 71 | const randomExponent = (digitCount = randomNonZeroInt(16)) => { 72 | if (digitCount < 1 || digitCount > 16 || digitCount !== ~~digitCount) { 73 | throw RangeError('digitCount'); 74 | } 75 | const max = digitCount === 16 ? MAXIMUM_DECIMAL_EXPONENT : (10 ** digitCount) - 1; 76 | const min = digitCount === 1 ? 0 : 10 ** (digitCount - 1); 77 | return randomIntInRange(min, max); 78 | }; 79 | 80 | const randomExponential = (digitCount = randomNonZeroInt(80), exponentDigitCount) => { 81 | let mantissa = randomNonZeroInt(9).toString(); 82 | if (digitCount > 1) mantissa += '.' + randomDigits(digitCount - 1); 83 | if (randomBool()) mantissa = '-' + mantissa; 84 | return mantissa + 'e' + (randomBool() ? '-' : '+') + randomExponent(exponentDigitCount); 85 | }; 86 | 87 | // Commonly includes repeated zeros and/or nines. 88 | const randomExponentialSpecial = (digitCount = randomNonZeroInt(80), exponentDigitCount) => { 89 | let mantissa = randomNonZeroInt(9).toString(); 90 | digitCount -= 1; 91 | if (digitCount > 0) { 92 | mantissa += '.'; 93 | while (digitCount !== 0) { 94 | // Call twice to bias towards smaller x but still allow possibility of x = digitCount. 95 | let x = randomNonZeroInt(randomNonZeroInt(digitCount)); 96 | digitCount -= x; 97 | mantissa += (randomBool() ? randomDigits(x) : (randomBool() ? '0' : '9').repeat(x)); 98 | } 99 | } 100 | if (Math.random() < 0.5) mantissa = '-' + mantissa; 101 | if (exponentDigitCount === undefined) { 102 | // Call three times to strongly bias towards exponents with few digits but with the maximum still possible. 103 | exponentDigitCount = randomNonZeroInt(randomNonZeroInt(randomNonZeroInt(16))); 104 | } 105 | return mantissa + 'e' + (randomBool() ? '-' : '+') + randomExponent(exponentDigitCount); 106 | }; 107 | 108 | const test = () => { 109 | const k = 100; 110 | log('\n randomInt(10)\n'); 111 | for (let i = 0; i < k; i++) log(randomInt(10)); 112 | log('\n randomNonZeroInt(10)\n'); 113 | for (let i = 0; i < k; i++) log(randomNonZeroInt(10)); 114 | log('\n randomIntLessThan(10)\n'); 115 | for (let i = 0; i < k; i++) log(randomIntLessThan(10)); 116 | log('\n randomIntInRange(5, 10)\n'); 117 | for (let i = 0; i < k; i++) log(randomIntInRange(5, 10)); 118 | log('\n randomUInt32()\n'); 119 | for (let i = 0; i < k; i++) log(randomUInt32()); 120 | log('\n randomBool()\n'); 121 | for (let i = 0; i < k; i++) log(randomBool()); 122 | log('\n randomDouble()\n'); 123 | for (let i = 0; i < k; i++) log(randomDouble()); 124 | log('\n randomDoubleFromBuffer()\n'); 125 | for (let i = 0; i < k; i++) log(randomDoubleFromBuffer()); 126 | log('\n randomDoubleLimitExponent(30)\n'); 127 | for (let i = 0; i < k; i++) log(randomDoubleLimitExponent(30)); 128 | log('\n randomNormalDouble()\n'); 129 | for (let i = 0; i < k; i++) log(randomNormalDouble()); 130 | log('\n randomSubnormalDouble()\n'); 131 | for (let i = 0; i < k; i++) log(randomSubnormalDouble()); 132 | log('\n randomDigits(20)\n'); 133 | for (let i = 0; i < k; i++) log(randomDigits(20)); 134 | log('\n randomExponent()\n'); 135 | for (let i = 0; i < k; i++) log(randomExponent()); 136 | log('\n randomExponential()\n'); 137 | for (let i = 0; i < k; i++) log(randomExponential()); 138 | log('\n randomExponentialSpecial(80)\n'); 139 | for (let i = 0; i < k; i++) log(randomExponentialSpecial(80)); 140 | log(); 141 | }; 142 | 143 | //test(); 144 | 145 | export { 146 | randomInt, 147 | randomNonZeroInt, 148 | randomIntLessThan, 149 | randomIntInRange, 150 | randomUInt32, 151 | randomBool, 152 | randomDouble, 153 | randomNormalDouble, 154 | randomSubnormalDouble, 155 | randomDoubleFromBuffer, 156 | randomDoubleLimitExponent, 157 | randomDigits, 158 | randomExponent, 159 | randomExponential, 160 | randomExponentialSpecial, 161 | test as randomTest 162 | }; 163 | -------------------------------------------------------------------------------- /serialize/test/test.js: -------------------------------------------------------------------------------- 1 | import { performance } from 'perf_hooks'; 2 | import Decimal from 'decimal.js'; 3 | import { serialize } from '../index.js'; 4 | import { 5 | randomNonZeroInt, 6 | randomDouble, 7 | randomDoubleLimitExponent, 8 | randomExponential, 9 | randomExponentialSpecial, 10 | } from "./random.js"; 11 | 12 | serialize.extend(Decimal); 13 | 14 | const DEFAULT_TEST_COUNT = 100000; 15 | 16 | const log = console.log; 17 | const hideCursor = () => void process.stdout.write("\x1B[?25l"); 18 | const showCursor = () => void process.stdout.write("\x1B[?25h"); 19 | 20 | const test = (value, show = false) => { 21 | const x = new Decimal(value); 22 | const xBuffer = x.toArrayBuffer(); 23 | const xArray = new Uint8Array(xBuffer); 24 | 25 | const y = Decimal.fromArrayBuffer(xBuffer); 26 | const yBuffer = y.toArrayBuffer(); 27 | const yArray = new Uint8Array(yBuffer); 28 | 29 | let pass = (x.eq(y) || x.isNaN() && y.isNaN()) && xArray.length === yArray.length; 30 | for (let i = 0; pass && i < xArray.length; i++) pass = xArray[i] === yArray[i]; 31 | 32 | if (!pass || show) { 33 | log(`x: ${String(x)}`); 34 | log(`x.s: ${String(x.s)}`); 35 | log(`x.e: ${String(x.e)}`); 36 | log(`x.d: ${x.d == null ? x.d : '[' + x.d + ']'}`); 37 | log(`x as Uint8Array: [${String(xArray)}]`); 38 | log(); 39 | log(`y: ${String(y)}`); 40 | log(`y.s: ${String(y.s)}`); 41 | log(`y.e: ${String(y.e)}`); 42 | log(`y.d: ${y.d == null ? y.d : '[' + y.d + ']'}`); 43 | log(`y as Uint8Array: [${String(yArray)}]`); 44 | log(); 45 | log(`Test ${pass ? 'passed' : 'failed'}`); 46 | process.exit(); 47 | } 48 | }; 49 | 50 | const arg1 = process.argv[2]; 51 | const arg2 = process.argv[3]; 52 | if (arg1 && arg2) test(arg1, true); 53 | 54 | test(NaN); 55 | test(Infinity); 56 | test(-Infinity); 57 | test(0); 58 | test(-0); 59 | test(1); 60 | test(-1); 61 | test(24); 62 | test(25); 63 | test(26); 64 | test(48); 65 | test(50); 66 | test(51); 67 | test('9.999999999999999e+30'); 68 | test('-1.00000000000000000000000000000000001e+31'); 69 | 70 | hideCursor(); 71 | const count = Math.max(Math.floor((isFinite(arg1) ? arg1 : DEFAULT_TEST_COUNT) / 5), 1); 72 | const start = performance.now(); 73 | 74 | for (let i = 0; i < count; ) { 75 | test(randomDouble(true)); 76 | test(randomDoubleLimitExponent(30)); 77 | test(randomExponential(randomNonZeroInt(40), randomNonZeroInt(4))); 78 | test(randomExponential(randomNonZeroInt(100))); 79 | test(randomExponentialSpecial(randomNonZeroInt(400))); 80 | if (++i % 2000 === 0) { 81 | process.stdout.cursorTo(0); 82 | process.stdout.write(String(i * 5)); 83 | } 84 | } 85 | 86 | const end = performance.now(); 87 | process.stdout.cursorTo(0); 88 | process.stdout.write(`${count * 5} tests completed in ${((end - start) / 1e3).toFixed(3)} secs.`); 89 | showCursor(); 90 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import Decimal from 'decimal.js'; 2 | import { evaluate, serialize } from './index.js'; 3 | 4 | evaluate.extend(Decimal); 5 | serialize.extend(Decimal); 6 | 7 | const evaluateTest = () => Decimal.evaluate('0.1 + 0.2').eq(0.3); 8 | 9 | const serializeTest = () => { 10 | const x = new Decimal('1234567899.54645456456456546213253466'); 11 | const buffer = x.toArrayBuffer(); 12 | const y = Decimal.fromArrayBuffer(buffer); 13 | return x.eq(y) 14 | }; 15 | 16 | console.log(`The evaluate extension test ${evaluateTest() ? 'passed' : 'failed'}`); 17 | console.log(`The serialize extension test ${serializeTest() ? 'passed' : 'failed'}`); --------------------------------------------------------------------------------