├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc └── robustr.pdf ├── package.json ├── rollup.config.js ├── src ├── BaseInfo32.ts ├── BigComplex.ts ├── BigFloat32.ts ├── BigFloat53.ts ├── BigFloatBase.ts ├── index.ts ├── ts-esm.json ├── tsconfig.json └── util.ts └── test ├── .gitignore ├── test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | package-lock.json 4 | *.lock 5 | *.log.* 6 | *.log 7 | *.tgz 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | src/ 4 | doc/ 5 | package-lock.json 6 | .travis.yml 7 | *.lock 8 | *.log.* 9 | *.log 10 | *.tgz 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "10" 5 | - "8" 6 | - "7" 7 | - "6" 8 | - "4" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015- BusFaster Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bigfloat 2 | 3 | [![build status](https://travis-ci.org/charto/bigfloat.svg?branch=master)](http://travis-ci.org/charto/bigfloat) 4 | [![npm version](https://img.shields.io/npm/v/bigfloat.svg)](https://www.npmjs.com/package/bigfloat) 5 | [![dependency status](https://david-dm.org/charto/bigfloat.svg)](https://david-dm.org/charto/bigfloat) 6 | [![install size](https://packagephobia.now.sh/badge?p=bigfloat)](https://packagephobia.now.sh/result?p=bigfloat) 7 | [![license](https://img.shields.io/npm/l/bigfloat.svg)](https://raw.githubusercontent.com/charto/bigfloat/master/LICENSE) 8 | 9 | `bigfloat` is a fast arbitrary precision math library optimized for computational geometry and geoinformatics. 10 | It provides binary floating point: 11 | 12 | - conversion to / from the JavaScript number type, `x = new BigFloat32(123.456)` and `x.valueOf()` 13 | - addition, `x.add(y)` 14 | - subtraction, `x.sub(y)` 15 | - multiplication, `x.mul(y)` 16 | - comparison, `x.deltaFrom(y)` alias `x.cmp(y)` 17 | - string output in even bases 2-36, `x.toString(10)` 18 | - string parsing in bases 2-36, `x = new BigFloat32('abc.def', 16)` 19 | 20 | without ever losing any significant bits. Numbers are immutable in the above operations, so they return a new BigFloat. 21 | For efficiency, the following methods instead destructively change the value: 22 | 23 | - `x.truncate(limbs)` rounds the fractional digits towards zero, to `limbs * 32` or `limbs * 53` bits. 24 | - `x.round(digits)` rounds approximately to `digits` decimal places (to enough limbs to hold them). 25 | 26 | Division is deliberately unsupported, because its result is generally inexact. 27 | Please multiply by the reciprocal or use rational numbers instead. 28 | Note that floating point values in numerators and denominators are perfectly cromulent. 29 | If you need square roots or transcendental functions, use some other library. 30 | 31 | There are two versions of the class, `BigFloat32` and `BigFloat53` with the same API but completely different internals as follows: 32 | 33 | **BigFloat32** 34 | 35 | - Arbitrarily long sequence of bits split into 32-bit integers ("limbs", effectively digits in base `2 ** 32`), 36 | somewhat like in the [GMP](https://gmplib.org/manual/Float-Internals.html) library. 37 | - The decimal point is a position between limbs, splitting the list of limbs into integer and fractional halves. 38 | - Dense representation: all bits between the most and least significant are stored. 39 | - Optimized for exponents relatively close to zero, so the location of the decimal point is always present in the limb array, 40 | even if that introduces otherwise insignificant leading or trailing zero limbs. 41 | - Precision is only limited by available memory. 42 | - Uses integer math for best portability. 43 | - Faster for operations between two arbitrary precision `BigFloat32` objects, slower for converting to / from JavaScript numbers. 44 | 45 | **BigFloat53** 46 | 47 | - Floating point expansion consisting of an unevaluated sum of components 48 | (JavaScript floating point numbers) ordered by increasing magnitude. 49 | - Multiple representations exist for each number, depending on how bits are split between components 50 | (this is transparent: they still compare as equal). 51 | - Each component can hold 1-53 bits of significand. 52 | - Sparse representation: components consisting entirely of zeroes are not stored. 53 | - Precision is limited by exponents representable in IEEE 754. 54 | - Binary digits at positions less significant than `2 ** -1074` (smallest double precision denormal) will disappear. 55 | - Numbers rounding to `2 ** 1024` or greater will overflow **spectacularly**. 56 | - Uses error free transformations (see JR Shewchuk. 57 | [*Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates*](doc/robustr.pdf), 58 | 1997). 59 | - Requires accurate rounding to nearest with ties to even as specified by EcmaScript 5.1 and up 60 | ([section 8.5](https://www.ecma-international.org/ecma-262/5.1/#sec-8.5)). 61 | - Faster for operations between arbitrary precision `BigFloat53` objects and JavaScript numbers, slower for operations between two arbitrary precision objects. 62 | 63 | In both versions the least significant limb / component is stored first, 64 | because basic algorithms for arithmetic operations progress from the least to most significant digit while propagating carry. 65 | If carry causes the output to grow, adding a new limb at the end of the array is faster than adding it in the beginning. 66 | 67 | TL;DR: Use `BigFloat32` for long operations between arbitrary precision floats, portability and to avoid under / overflow. 68 | Use `BigFloat53` for short calculations with many ordinary JavaScript numbers as inputs. 69 | 70 | You may want to test with both to compare their speed and see if you run into overflow 71 | or any floating point portability issues on mobile platforms. 72 | 73 | ## Optimization 74 | 75 | In any longer iterated calculations involving multiplication, `truncate` should be called regularly because otherwise significant bits will keep accumulating. 76 | For example, squaring a number doubles the number of bits at every step, easily turning an algorithm with linear complexity into a quadratic one 77 | (both in speed and space). 78 | 79 | To avoid surprises, the basic operations allocate new objects for storing results. A second parameter can be given, 80 | with a result `BigFloat` object of the same type (32 or 53). Its contents will be destructively overwritten with the result, 81 | to save a memory allocation. This avoids garbage collection related slowdowns in longer calculations. 82 | 83 | Some care is needed in re-using temporary variables, because inputs cannot be simultaneously used as results: 84 | 85 | ```TypeScript 86 | x.add(y, w).sub(z, w) 87 | ``` 88 | 89 | fails because in the subtraction, `w` is both the subtrahend and the difference. 90 | 91 | Existing objects can also be re-initialized with: 92 | 93 | - zero, `x.setZero()` 94 | - new value from a JavaScript number, `x.setValue(123.456)` 95 | 96 | Additionally, `BigFloat53` objects support initialization from results of operations between two JavaScript numbers: 97 | 98 | - sum, `x.setSum(12.34, 56.78)` 99 | - product, `x.setProduct(12.34, 56.78)` 100 | 101 | These use very fast double double arithmetic (error free transformations). 102 | 103 | ## Speed 104 | 105 | It's fast, see the [Mandelbrot benchmark](http://charto.github.io/bigfloat/). Here's some example results: 106 | 107 | Native JavaScript IEEE 754: 108 | ████████████████████████████████ // ██ 80000 frames per minute 109 | 110 | `bigfloat`: 111 | ████████████████████████████ 141 frames per minute 112 | 113 | [bignumber.js](https://github.com/MikeMcl/bignumber.js): 114 | ██████████ 48 frames per minute 115 | 116 | [big.js](https://github.com/MikeMcl/big.js): 117 | ███████ 35 frames per minute 118 | 119 | Getting started 120 | --- 121 | 122 | ```bash 123 | git clone https://github.com/charto/bigfloat.git node_modules/bigfloat 124 | cd node_modules/bigfloat && npm install 125 | cd ../.. 126 | node 127 | ``` 128 | 129 | OR 130 | 131 | ```bash 132 | npm install bigfloat 133 | node 134 | ``` 135 | 136 | THEN 137 | 138 | ```js 139 | x = Math.pow(2, 53); 140 | console.log(x + 1 - x); // Prints 0 141 | 142 | BigFloat32 = require('bigfloat').BigFloat32; 143 | console.log(new BigFloat32(x).add(1).sub(x).toString()); // Prints 1 144 | ``` 145 | 146 | # License 147 | 148 | [The MIT License](https://raw.githubusercontent.com/charto/bigfloat/master/LICENSE) 149 | 150 | Copyright (c) 2015- BusFaster Ltd 151 | 152 | The paper `doc/robustr.pdf` is copyright JR Shewchuk and licenced as detailed inside under "About this Report". 153 | -------------------------------------------------------------------------------- /doc/robustr.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charto/bigfloat/8386bc84835575f06dc15611eb920805336cee08/doc/robustr.pdf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bigfloat", 3 | "version": "0.1.1", 4 | "description": "Fast arbitrary precision math library for computational geometry.", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "browser": "dist/umd/index.js", 8 | "typings": "dist/esm/index.d.ts", 9 | "scripts": { 10 | "tsc": "tsc", 11 | "rollup": "rollup", 12 | "prepublish": "(checkver ge 5.0.0 && tsc -m es6 --outdir dist/esm -p src && rollup -c) || tsc -p src", 13 | "test": "tsc -p test && node test/test.js" 14 | }, 15 | "author": "Juha Järvi", 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/charto/bigfloat.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/charto/bigfloat/issues" 23 | }, 24 | "homepage": "https://github.com/charto/bigfloat#readme", 25 | "keywords": [ 26 | "bignum", 27 | "gmp" 28 | ], 29 | "devDependencies": { 30 | "@types/node": "^10.12.9", 31 | "autoroll": "^0.1.0", 32 | "rollup": "^0.67.3", 33 | "typescript": "^3.1.6" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('autoroll')(require('./package.json')); 2 | -------------------------------------------------------------------------------- /src/BaseInfo32.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | /** Base for calculations, the bigger the better but must fit in 32 bits. */ 5 | export const limbSize32 = Math.pow(2, 32); 6 | export const limbInv32 = Math.pow(2, -32); 7 | export const limbsPerDigit32 = Math.log(10) / (32 * Math.log(2)); 8 | 9 | /** Create a string with the given number of zero digits. */ 10 | 11 | function zeroes(count: number) { 12 | return(new Array(count + 1).join('0')); 13 | } 14 | 15 | export class BaseInfo32 { 16 | 17 | constructor(public base: number) {} 18 | 19 | static init(base: number) { 20 | return(BaseInfo32.baseTbl[base] || (BaseInfo32.baseTbl[base] = new BaseInfo32(base))); 21 | } 22 | 23 | private static baseTbl: { [base: number]: BaseInfo32 } = {}; 24 | 25 | /** Average number of digits per limb. */ 26 | limbDigitsExact = Math.log(limbSize32) / Math.log(this.base); 27 | /** Number of entire digits per limb. */ 28 | limbDigits = ~~this.limbDigitsExact; 29 | /** Maximum power of base that fits in a limb. */ 30 | limbBase = Math.pow(this.base, this.limbDigits); 31 | /** String of zeroes for padding an empty limb. */ 32 | pad = zeroes(this.limbDigits); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/BigComplex.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2018- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { BigFloatBase } from './BigFloatBase'; 5 | import { BigFloat32 } from './BigFloat32'; 6 | import { BigFloat53 } from './BigFloat53'; 7 | 8 | /** Simpler replacement for the default TypeScript helper. 9 | * Ignores static members and avoids rollup warnings. */ 10 | 11 | function __extends(child: new(...args: any[]) => Child, parent: new(...args: any[]) => Parent) { 12 | function helper(this: Parent) { this.constructor = child; } 13 | 14 | (helper as any as new() => Parent).prototype = parent.prototype; 15 | child.prototype = new (helper as any as new() => Parent)(); 16 | } 17 | 18 | export class BigComplex> { 19 | 20 | constructor( 21 | real?: Base | number | string, 22 | imag?: Base | number | string, 23 | base?: number 24 | ) { 25 | this.real = typeof(real) == 'object' ? real : new this.Base(real, base); 26 | this.imag = typeof(imag) == 'object' ? imag : new this.Base(imag, base); 27 | } 28 | 29 | clone() { 30 | const other = new (this.constructor as new(real: Base, imag: Base) => this)( 31 | this.real.clone(), 32 | this.imag.clone() 33 | ); 34 | 35 | return(other); 36 | } 37 | 38 | setZero() { 39 | this.real.setZero(); 40 | this.imag.setZero(); 41 | 42 | return(this); 43 | } 44 | 45 | setValue(other: BigComplex) { 46 | this.real.setValue(other.real); 47 | this.imag.setValue(other.imag); 48 | 49 | return(this); 50 | } 51 | 52 | mul(multiplier: number | Base | BigComplex, product?: BigComplex) { 53 | product = product || new (this.constructor as new() => this)(); 54 | 55 | if(multiplier instanceof BigComplex) { 56 | this.real.mul(multiplier.real, this.temp1); 57 | this.imag.mul(multiplier.imag, this.temp2); 58 | this.temp1.sub(this.temp2, product.real); 59 | 60 | this.real.mul(multiplier.imag, this.temp1); 61 | this.imag.mul(multiplier.real, this.temp2); 62 | this.temp1.add(this.temp2, product.imag); 63 | } else { 64 | this.real.mul(multiplier, product.real); 65 | this.imag.mul(multiplier, product.imag); 66 | } 67 | 68 | return(product); 69 | } 70 | 71 | sqr(product?: BigComplex) { 72 | product = product || new (this.constructor as new() => this)(); 73 | 74 | this.real.mul(this.real, this.temp1); 75 | this.imag.mul(this.imag, this.temp2); 76 | this.temp1.sub(this.temp2, product.real); 77 | 78 | this.real.mul(this.imag, this.temp1); 79 | this.temp1.add(this.temp1, product.imag); 80 | 81 | return(product); 82 | } 83 | 84 | add(addend: number | Base | BigComplex, sum?: BigComplex) { 85 | sum = sum || new (this.constructor as new() => this)(); 86 | 87 | if(addend instanceof BigComplex) { 88 | this.real.add(addend.real, sum.real); 89 | this.imag.add(addend.imag, sum.imag); 90 | } else { 91 | this.real.add(addend, sum.real); 92 | } 93 | 94 | return(sum); 95 | } 96 | 97 | sub(subtrahend: number | Base | BigComplex, difference?: BigComplex) { 98 | difference = difference || new (this.constructor as new() => this)(); 99 | 100 | if(subtrahend instanceof BigComplex) { 101 | this.real.sub(subtrahend.real, difference.real); 102 | this.imag.sub(subtrahend.imag, difference.imag); 103 | } else { 104 | this.real.sub(subtrahend, difference.real); 105 | } 106 | 107 | return(difference); 108 | } 109 | 110 | truncate(fractionLimbCount: number) { 111 | this.real.truncate(fractionLimbCount); 112 | this.imag.truncate(fractionLimbCount); 113 | 114 | return(this); 115 | } 116 | 117 | Base: new(x?: Base | number | string, base?: number) => Base; 118 | real: Base; 119 | imag: Base; 120 | 121 | temp1: Base; 122 | temp2: Base; 123 | 124 | } 125 | 126 | export class BigComplex32 extends BigComplex {} 127 | export class BigComplex53 extends BigComplex {} 128 | 129 | BigComplex32.prototype.Base = BigFloat32; 130 | BigComplex32.prototype.temp1 = new BigFloat32(); 131 | BigComplex32.prototype.temp2 = new BigFloat32(); 132 | 133 | BigComplex53.prototype.Base = BigFloat53; 134 | BigComplex53.prototype.temp1 = new BigFloat53(); 135 | BigComplex53.prototype.temp2 = new BigFloat53(); 136 | -------------------------------------------------------------------------------- /src/BigFloat32.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { BaseInfo32, limbSize32, limbInv32, limbsPerDigit32 } from './BaseInfo32'; 5 | import { BigFloatBase } from './BigFloatBase'; 6 | import { trimNumber } from './util'; 7 | 8 | export class BigFloat32 implements BigFloatBase { 9 | 10 | constructor(value?: BigFloat32 | number | string, base?: number) { 11 | value ? this.setValue(value, base) : this.setZero(); 12 | } 13 | 14 | clone() { 15 | return(new BigFloat32().setBig(this)); 16 | } 17 | 18 | setZero() { 19 | this.sign = 1; 20 | this.fractionLen = 0; 21 | this.len = 0; 22 | 23 | return(this); 24 | } 25 | 26 | setValue(other: BigFloat32 | number | string, base?: number) { 27 | if(typeof(other) == 'number') { 28 | return(this.setNumber(other)); 29 | } 30 | 31 | if(other instanceof BigFloat32) { 32 | return(this.setBig(other)); 33 | } 34 | 35 | return(this.setString(other.toString(), base || 10)); 36 | } 37 | 38 | private setBig(other: BigFloat32) { 39 | const len = other.len; 40 | 41 | this.sign = other.sign; 42 | this.fractionLen = other.fractionLen; 43 | this.len = len; 44 | 45 | for(let pos = 0; pos < len; ++pos) { 46 | this.limbList[pos] = other.limbList[pos]; 47 | } 48 | 49 | return(this); 50 | } 51 | 52 | /** Set value from a floating point number (probably IEEE 754 double). */ 53 | 54 | private setNumber(value: number) { 55 | if(value < 0) { 56 | value = -value; 57 | this.sign = -1; 58 | } else { 59 | this.sign = 1; 60 | } 61 | 62 | let iPart = Math.floor(value); 63 | let fPart = value - iPart; 64 | let fractionLen = 0; 65 | 66 | const limbList = this.limbList; 67 | let limb: number; 68 | let len = 0; 69 | 70 | // Handle fractional part. 71 | while(fPart) { 72 | // Extract limbs starting from the most significant. 73 | fPart *= limbSize32; 74 | limb = fPart >>> 0; 75 | fPart -= limb; 76 | 77 | // Append limb to value (limbs are reversed later). 78 | limbList[len++] = limb; 79 | ++fractionLen; 80 | } 81 | 82 | // Reverse array from 0 to len. 83 | let pos = 0; 84 | 85 | while(--len > pos) { 86 | limb = limbList[pos]; 87 | limbList[pos++] = limbList[len]; 88 | limbList[len] = limb; 89 | } 90 | 91 | len += pos + 1; 92 | 93 | // Handle integer part. 94 | 95 | while(iPart) { 96 | // Extract limbs starting from the least significant. 97 | 98 | limb = iPart % limbSize32; // Could also be iPart >>> 0 99 | iPart = (iPart - limb) / limbSize32; 100 | 101 | // Append limb to value. 102 | limbList[len++] = limb; 103 | } 104 | 105 | this.limbList = limbList; 106 | this.fractionLen = fractionLen; 107 | this.len = len; 108 | 109 | return(this); 110 | } 111 | 112 | private parseFraction(value: string, base: number, start: number, offset: number, limbBase: number, limbDigits: number) { 113 | const limbList = this.limbList; 114 | let pos = value.length; 115 | 116 | // Set limbs to zero, because divInt uses them as input. 117 | 118 | let limbNum = offset - 1; 119 | 120 | while(limbNum) { 121 | limbList[--limbNum] = 0; 122 | } 123 | 124 | // Read initial digits so their count becomes divisible by limbDigits. 125 | 126 | let posNext = pos - ((pos - start + limbDigits - 1) % limbDigits + 1); 127 | 128 | limbList[offset - 1] = parseInt(value.substr(posNext, pos - posNext), base); 129 | this.divInt(Math.pow(base, pos - posNext), offset); 130 | 131 | pos = posNext; 132 | 133 | // Read rest of the digits in limbDigits sized chunks. 134 | 135 | while(pos > start) { 136 | pos -= limbDigits; 137 | 138 | limbList[offset - 1] = parseInt(value.substr(pos, limbDigits), base); 139 | 140 | // Divide by maximum power of base that fits in a limb. 141 | this.divInt(limbBase, offset); 142 | } 143 | } 144 | 145 | private setString(value: string, base: number) { 146 | const { limbBase, limbDigits, limbDigitsExact } = BaseInfo32.init(base); 147 | const limbList = this.limbList; 148 | let pos = -1; 149 | let c: string; 150 | 151 | this.sign = 1; 152 | 153 | // Handle leading signs and zeroes. 154 | 155 | while(1) { 156 | c = value.charAt(++pos); 157 | 158 | switch(c) { 159 | case '-': 160 | this.sign = -1; 161 | case '+': 162 | case '0': 163 | continue; 164 | } 165 | 166 | break; 167 | } 168 | 169 | const posDot = (value.indexOf('.', pos) + 1 || value.length + 1) - 1; 170 | 171 | // Handle fractional part. 172 | 173 | if(posDot < value.length - 1) { 174 | // Reserve enough limbs to contain digits in fractional part. 175 | const len = ~~((value.length - posDot - 1) / limbDigitsExact) + 1; 176 | 177 | this.parseFraction(value, base, posDot + 1, len + 1, limbBase, limbDigits); 178 | 179 | this.fractionLen = len; 180 | this.len = len; 181 | 182 | // Remove trailing zeroes. 183 | this.trimLeast(); 184 | } else { 185 | this.fractionLen = 0; 186 | this.len = 0; 187 | } 188 | 189 | const offset = this.fractionLen; 190 | 191 | // Handle integer part. 192 | 193 | if(posDot > pos) { 194 | // Read initial digits so their count becomes divisible by limbDigits. 195 | 196 | let posNext = pos + (posDot - pos + limbDigits - 1) % limbDigits + 1; 197 | 198 | ++this.len; 199 | limbList[offset] = parseInt(value.substr(pos, posNext - pos), base); 200 | pos = posNext; 201 | 202 | // Read rest of the digits in limbDigits sized chunks. 203 | 204 | while(pos < posDot) { 205 | // Multiply by maximum power of base that fits in a limb. 206 | if(this.mulInt(limbBase, limbList, offset, offset, 0)) ++this.len; 207 | 208 | // Add latest limb. 209 | limbList[offset] += parseInt(value.substr(pos, limbDigits), base); 210 | 211 | pos += limbDigits; 212 | } 213 | } 214 | 215 | return(this); 216 | } 217 | 218 | /** Trim zero limbs from most significant end. */ 219 | 220 | private trimMost() { 221 | const limbList = this.limbList; 222 | const fractionLen = this.fractionLen; 223 | let len = this.len; 224 | 225 | while(len > fractionLen && !limbList[len - 1]) --len; 226 | 227 | this.len = len; 228 | } 229 | 230 | /** Trim zero limbs from least significant end. */ 231 | 232 | private trimLeast() { 233 | const limbList = this.limbList; 234 | let len = this.fractionLen; 235 | let pos = 0; 236 | 237 | while(pos < len && !limbList[pos]) ++pos; 238 | 239 | if(pos) this.truncate(len - pos); 240 | } 241 | 242 | /** Multiply by an integer and write output limbs to another list. */ 243 | 244 | private mulInt(factor: number, dstLimbList: number[], srcPos: number, dstPos: number, overwriteMask: number) { 245 | if(!factor) return(0); 246 | 247 | const limbList = this.limbList; 248 | const limbCount = this.len; 249 | let limb: number; 250 | let lo: number; 251 | let carry = 0; 252 | 253 | // limbList is an array of 32-bit ints but split here into 16-bit low 254 | // and high words for multiplying by a 32-bit term, so the intermediate 255 | // 48-bit multiplication results fit into 53 bits of IEEE 754 mantissa. 256 | 257 | while(srcPos < limbCount) { 258 | limb = limbList[srcPos++]; 259 | 260 | // Multiply lower half of limb with factor, making carry temporarily take 48 bits. 261 | carry += factor * (limb & 0xffff); 262 | // Get lowest 16 bits of full product. 263 | lo = carry & 0xffff; 264 | // Right shift by dividing because >> and >>> truncate to 32 bits before shifting. 265 | carry = (carry - lo) / 65536; 266 | 267 | // Multiply higher half of limb and combine with lowest 16 bits of full product. 268 | carry += factor * (limb >>> 16); 269 | lo |= carry << 16; 270 | // Lowest 32 bits of full product are added to output limb. 271 | limb = ((dstLimbList[dstPos] & overwriteMask) + lo) >>> 0; 272 | dstLimbList[dstPos++] = limb; 273 | 274 | // Highest 32 bits of full product stay in carry, also increment by 1 if previous sum overflowed. 275 | carry = (carry / 65536) >>> 0; 276 | // Bit twiddle equivalent to: if(limb < (lo >>> 0)) ++carry; 277 | carry += (lo ^ (((limb - lo) ^ lo) & ~(limb ^ lo))) >>> 31; 278 | } 279 | 280 | // Extend result by one more limb if it overflows. 281 | if(carry) dstLimbList[dstPos] = carry; 282 | 283 | return(carry); 284 | } 285 | 286 | private mulBig(multiplier: BigFloat32, product: BigFloat32) { 287 | if(this.isZero() || multiplier.isZero()) return(product.setZero()); 288 | 289 | const multiplierLimbs = multiplier.limbList; 290 | const lenMultiplier = multiplier.len; 291 | const productLimbs = product.limbList; 292 | 293 | let posProduct = this.len + lenMultiplier; 294 | product.len = posProduct; 295 | 296 | // TODO: Only clear from len to len + lenMultiplier 297 | while(posProduct--) { 298 | productLimbs[posProduct] = 0; 299 | } 300 | 301 | this.mulInt(multiplierLimbs[0], productLimbs, 0, 0, 0); 302 | 303 | for(let posMultiplier = 1; posMultiplier < lenMultiplier; ++posMultiplier) { 304 | this.mulInt(multiplierLimbs[posMultiplier], productLimbs, 0, posMultiplier, 0xffffffff); 305 | } 306 | 307 | product.sign = this.sign * multiplier.sign as (-1 | 1); 308 | product.fractionLen = this.fractionLen + multiplier.fractionLen; 309 | 310 | product.trimMost(); 311 | product.trimLeast(); 312 | 313 | return(product); 314 | } 315 | 316 | /** Multiply and return product in a new BigFloat32. */ 317 | 318 | mul(multiplier: number | BigFloat32, product?: BigFloat32) { 319 | product = product || new BigFloat32(); 320 | 321 | if(typeof(multiplier) == 'number') { 322 | multiplier = temp32.setNumber(multiplier); 323 | } 324 | 325 | if(product == this) throw(new Error('Multiplication in place is unsupported')); 326 | 327 | return(this.mulBig(multiplier, product)); 328 | } 329 | 330 | absDeltaFrom(other: number | BigFloat32) { 331 | if(typeof(other) == 'number') { 332 | other = temp32.setNumber(other); 333 | } 334 | 335 | const limbList = this.limbList; 336 | const otherList = other.limbList; 337 | let limbCount = this.len; 338 | let otherCount = other.len; 339 | 340 | // Compare lengths. 341 | // Note: leading zeroes in integer part must be trimmed for this to work! 342 | let d = (limbCount - this.fractionLen) - (otherCount - other.fractionLen); 343 | // If lengths are equal, compare each limb from most to least significant. 344 | while(!d && limbCount && otherCount) d = limbList[--limbCount] - otherList[--otherCount]; 345 | 346 | if(d) return(d); 347 | 348 | if(limbCount) { 349 | do d = limbList[--limbCount]; while(!d && limbCount); 350 | } else if(otherCount) { 351 | do d = -otherList[--otherCount]; while(!d && otherCount); 352 | } 353 | 354 | return(d); 355 | } 356 | 357 | cmp: (other: number | BigFloat32) => number; 358 | 359 | isZero() { 360 | return(this.len == 0); 361 | } 362 | 363 | getSign() { 364 | return(this.len && this.sign); 365 | } 366 | 367 | /** Return an arbitrary number with sign matching the result of this - other. */ 368 | 369 | deltaFrom(other: number | BigFloat32) { 370 | if(typeof(other) == 'number') { 371 | other = temp32.setNumber(other); 372 | } 373 | 374 | return( 375 | // Make positive and negative zero equal. 376 | this.len + other.len && ( 377 | // Compare signs. 378 | this.sign - other.sign || 379 | // Finally compare full values. 380 | this.absDeltaFrom(other) * this.sign 381 | ) 382 | ); 383 | } 384 | 385 | private addBig(addend: BigFloat32, sum: BigFloat32) { 386 | let augend: BigFloat32 = this; 387 | 388 | let fractionLen = augend.fractionLen; 389 | let len = fractionLen - addend.fractionLen; 390 | 391 | if(len < 0) { 392 | len = -len; 393 | fractionLen += len; 394 | augend = addend; 395 | addend = this; 396 | } 397 | 398 | sum.sign = this.sign; 399 | sum.fractionLen = fractionLen; 400 | 401 | let sumLimbs = sum.limbList; 402 | let augendLimbs = augend.limbList; 403 | let addendLimbs = addend.limbList; 404 | let posAugend = 0; 405 | let posAddend = 0; 406 | let carry = 0; 407 | let limbSum: number; 408 | 409 | // If one input has more fractional limbs, just copy the leftovers to output. 410 | 411 | while(posAugend < len) { 412 | sumLimbs[posAugend] = augendLimbs[posAugend]; 413 | ++posAugend; 414 | } 415 | 416 | let lenAddend = addend.len; 417 | 418 | len = augend.len - posAugend; 419 | if(len > lenAddend) len = lenAddend; 420 | 421 | // Calculate sum where input numbers overlap. 422 | 423 | while(posAddend < len) { 424 | carry += augendLimbs[posAugend] + addendLimbs[posAddend++]; 425 | limbSum = carry >>> 0; 426 | carry = carry - limbSum && 1; 427 | 428 | sumLimbs[posAugend++] = limbSum; 429 | } 430 | 431 | let posSum = posAugend; 432 | 433 | if(len < lenAddend) { 434 | len = lenAddend; 435 | augend = addend; 436 | posAugend = posAddend; 437 | augendLimbs = addendLimbs; 438 | } else len = augend.len; 439 | 440 | // Copy leftover most significant limbs to output, propagating carry. 441 | 442 | while(posAugend < len) { 443 | carry += augendLimbs[posAugend++]; 444 | limbSum = carry >>> 0; 445 | carry = carry - limbSum && 1; 446 | 447 | sumLimbs[posSum++] = limbSum; 448 | } 449 | 450 | if(carry) sumLimbs[posSum++] = carry; 451 | 452 | sum.len = posSum; 453 | sum.trimLeast(); 454 | 455 | return(sum); 456 | } 457 | 458 | private subBig(subtrahend: BigFloat32, difference: BigFloat32) { 459 | let minuend: BigFloat32 = this; 460 | 461 | difference.sign = this.sign; 462 | 463 | // Make sure the subtrahend is the smaller number. 464 | if(minuend.absDeltaFrom(subtrahend) < 0) { 465 | minuend = subtrahend; 466 | subtrahend = this; 467 | difference.sign = -this.sign as (-1 | 1); 468 | } 469 | 470 | let fractionLen = minuend.fractionLen; 471 | let len = fractionLen - subtrahend.fractionLen; 472 | 473 | let differenceLimbs = difference.limbList; 474 | let minuendLimbs = minuend.limbList; 475 | let subtrahendLimbs = subtrahend.limbList; 476 | let lenMinuend = minuend.len; 477 | let lenSubtrahend = subtrahend.len; 478 | let lenFinal = lenMinuend; 479 | let posMinuend = 0; 480 | let posSubtrahend = 0; 481 | let posDifference = 0; 482 | let carry = 0; 483 | let limbDiff: number; 484 | 485 | if(len >= 0) { 486 | while(posMinuend < len) { 487 | differenceLimbs[posMinuend] = minuendLimbs[posMinuend]; 488 | ++posMinuend; 489 | } 490 | 491 | len += lenSubtrahend; 492 | if(len > lenMinuend) len = lenMinuend; 493 | 494 | posDifference = posMinuend; 495 | } else { 496 | len = -len; 497 | fractionLen += len; 498 | lenFinal += len; 499 | 500 | while(posSubtrahend < len) { 501 | carry -= subtrahendLimbs[posSubtrahend]; 502 | limbDiff = carry >>> 0; 503 | carry = -(carry < 0); 504 | 505 | differenceLimbs[posSubtrahend++] = limbDiff; 506 | } 507 | 508 | len += lenMinuend; 509 | if(len > lenSubtrahend) len = lenSubtrahend; 510 | 511 | posDifference = posSubtrahend; 512 | } 513 | 514 | difference.fractionLen = fractionLen; 515 | 516 | // Calculate difference where input numbers overlap. 517 | 518 | while(posDifference < len) { 519 | carry += minuendLimbs[posMinuend++] - subtrahendLimbs[posSubtrahend++]; 520 | limbDiff = carry >>> 0; 521 | carry = -(carry < 0); 522 | 523 | differenceLimbs[posDifference++] = limbDiff; 524 | } 525 | 526 | // Copy leftover most significant limbs to output, propagating carry. 527 | 528 | while(posDifference < lenFinal) { 529 | carry += minuendLimbs[posMinuend++]; 530 | limbDiff = carry >>> 0; 531 | carry = -(carry < 0); 532 | 533 | differenceLimbs[posDifference++] = limbDiff; 534 | } 535 | 536 | difference.len = posDifference; 537 | difference.trimMost(); 538 | difference.trimLeast(); 539 | 540 | return(difference); 541 | } 542 | 543 | private addSub(addend: number | BigFloat32, sign: -1 | 1, result?: BigFloat32) { 544 | result = result || new BigFloat32(); 545 | 546 | if(result == this) throw(new Error('Addition and subtraction in place is unsupported')); 547 | 548 | if(typeof(addend) == 'number') { 549 | addend = temp32.setNumber(addend); 550 | } 551 | 552 | if(this.sign * addend.sign * sign < 0) { 553 | return(this.subBig(addend, result)); 554 | } else { 555 | return(this.addBig(addend, result)); 556 | } 557 | } 558 | 559 | /** Add and return sum in a new BigFloat32. */ 560 | 561 | add(addend: number | BigFloat32, sum?: BigFloat32) { 562 | return(this.addSub(addend, 1, sum)); 563 | } 564 | 565 | /** Subtract and return difference in a new BigFloat32. */ 566 | 567 | sub(subtrahend: number | BigFloat32, difference?: BigFloat32) { 568 | return(this.addSub(subtrahend, -1, difference)); 569 | } 570 | 571 | /** Round towards zero, to given number of base 2^32 fractional digits. */ 572 | 573 | truncate(fractionLimbCount: number) { 574 | const diff = this.fractionLen - fractionLimbCount; 575 | 576 | if(diff > 0) { 577 | this.fractionLen = fractionLimbCount; 578 | this.len -= diff; 579 | const len = this.len; 580 | const limbList = this.limbList; 581 | 582 | for(let pos = 0; pos < len; ++pos) { 583 | limbList[pos] = limbList[pos + diff]; 584 | } 585 | } 586 | 587 | return(this); 588 | } 589 | 590 | round(decimalCount: number) { 591 | return(this.truncate(1 + ~~(decimalCount * limbsPerDigit32))); 592 | } 593 | 594 | /** Divide by integer, replacing current value by quotient. Return integer remainder. */ 595 | 596 | private divInt(divisor: number, pos: number) { 597 | let limbList = this.limbList; 598 | let limb: number; 599 | let hi: number, lo: number; 600 | let carry = 0; 601 | 602 | // If most significant limb is zero after dividing, decrement number of limbs remaining. 603 | if(limbList[pos - 1] < divisor) { 604 | carry = limbList[--pos]; 605 | this.len = pos; 606 | } 607 | 608 | while(pos--) { 609 | limb = limbList[pos]; 610 | 611 | carry = carry * 0x10000 + (limb >>> 16); 612 | hi = (carry / divisor) >>> 0; 613 | carry = carry - hi * divisor; 614 | 615 | carry = carry * 0x10000 + (limb & 0xffff); 616 | lo = (carry / divisor) >>> 0; 617 | carry = carry - lo * divisor; 618 | 619 | limbList[pos] = ((hi << 16) | lo) >>> 0; 620 | } 621 | 622 | return(carry); 623 | } 624 | 625 | private fractionToString(base: number, digitList: string[]) { 626 | const { pad, limbBase } = BaseInfo32.init(base); 627 | let limbList = this.limbList; 628 | let limbCount = this.fractionLen; 629 | let limbNum = 0; 630 | let limbStr: string; 631 | 632 | if(base & 1) { 633 | throw(new Error('Conversion of floating point values to odd bases is unsupported')); 634 | } 635 | 636 | // Skip least significant limbs that equal zero. 637 | while(limbNum < limbCount && !limbList[limbNum]) ++limbNum; 638 | if(limbNum >= limbCount) return; 639 | 640 | digitList.push('.'); 641 | 642 | const fPart = temp32; 643 | fPart.limbList = limbList.slice(limbNum, limbCount); 644 | fPart.len = limbCount - limbNum; 645 | 646 | limbNum = 0; 647 | 648 | while(limbNum < fPart.len) { 649 | if(fPart.limbList[limbNum]) { 650 | let carry = fPart.mulInt(limbBase, fPart.limbList, limbNum, limbNum, 0); 651 | 652 | limbStr = carry.toString(base); 653 | 654 | digitList.push(pad.substr(limbStr.length) + limbStr); 655 | } else ++limbNum; 656 | } 657 | } 658 | 659 | getExpansion(output: number[]) { 660 | const limbList = this.limbList; 661 | const len = this.len; 662 | let exp = this.sign; 663 | let pos = this.fractionLen; 664 | 665 | while(pos--) { 666 | exp *= limbInv32; 667 | } 668 | 669 | while(++pos < len) { 670 | output[pos] = limbList[pos] * exp; 671 | exp *= limbSize32; 672 | } 673 | 674 | return(len); 675 | } 676 | 677 | valueOf() { 678 | const limbList = this.limbList; 679 | let result = 0; 680 | let exp = limbInv32 * this.sign; 681 | let len = this.fractionLen; 682 | let pos = 0; 683 | 684 | while(pos < len) { 685 | result = result * limbInv32 + limbList[pos++]; 686 | } 687 | 688 | len = this.len; 689 | 690 | while(pos < len) { 691 | result = result * limbInv32 + limbList[pos++]; 692 | exp *= limbSize32; 693 | } 694 | 695 | return(result * exp); 696 | } 697 | 698 | /** Convert to string in any even base supported by Number.toString. 699 | * @return String in lower case. */ 700 | 701 | toString(base: number = 10) { 702 | const { pad, limbBase } = BaseInfo32.init(base); 703 | let digitList: string[] = []; 704 | 705 | let limbList = this.limbList; 706 | let limb: number; 707 | let limbStr: string; 708 | 709 | if(limbBase != limbSize32) { 710 | let iPart = temp32; 711 | iPart.limbList = limbList.slice(this.fractionLen, this.len); 712 | iPart.len = this.len - this.fractionLen; 713 | 714 | // Loop while 2 or more limbs remain, requiring arbitrary precision division to extract digits. 715 | while(iPart.len > 1) { 716 | limbStr = iPart.divInt(limbBase, iPart.len).toString(base); 717 | 718 | // Prepend digits into final result, padded with zeroes to 9 digits. 719 | // Since more limbs still remain, whole result will not have extra padding. 720 | digitList.push(pad.substr(limbStr.length) + limbStr); 721 | } 722 | 723 | // Prepend last remaining limb and sign to result. 724 | digitList.push('' + (iPart.limbList[0] || 0)); 725 | if(this.sign < 0) digitList.push('-'); 726 | 727 | digitList.reverse(); 728 | 729 | // Handle fractional part. 730 | 731 | this.fractionToString(base, digitList); 732 | } else { 733 | let limbNum = this.len; 734 | const fractionPos = this.fractionLen; 735 | 736 | if(this.sign < 0) digitList.push('-'); 737 | if(limbNum == fractionPos) digitList.push('0'); 738 | 739 | while(limbNum--) { 740 | limbStr = limbList[limbNum].toString(base); 741 | 742 | if(limbNum == fractionPos - 1) digitList.push('.'); 743 | digitList.push(pad.substr(limbStr.length) + limbStr); 744 | } 745 | } 746 | 747 | // Remove leading and trailing zeroes. 748 | return(trimNumber(digitList.join(''))); 749 | } 750 | 751 | private sign: -1 | 1; 752 | 753 | /** List of digits in base 2^32, least significant first. */ 754 | private limbList: number[] = []; 755 | /** Number of limbs belonging to fractional part. */ 756 | private fractionLen: number; 757 | private len: number; 758 | } 759 | 760 | BigFloat32.prototype.cmp = BigFloat32.prototype.deltaFrom; 761 | 762 | const temp32 = new BigFloat32(); 763 | -------------------------------------------------------------------------------- /src/BigFloat53.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2018- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | /* 5 | These algorithms are based on the paper: 6 | Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates 7 | Jonathan Richard Shewchuk 8 | Discrete & Computational Geometry 18(3):305–363, October 1997. 9 | */ 10 | 11 | import { BigFloatBase } from './BigFloatBase'; 12 | import { BigFloat32 } from './BigFloat32'; 13 | 14 | export const dekkerSplitter = (1 << 27) + 1; 15 | export const limbsPerDigit53 = Math.log(10) / (53 * Math.log(2)); 16 | 17 | /** See Shewchuk page 7. */ 18 | 19 | /* 20 | function fastTwoSum(a: number, b: number, sum: number[]) { 21 | const estimate = a + b; 22 | 23 | sum[0] = b - (estimate - a); 24 | sum[1] = estimate; 25 | 26 | return(sum); 27 | } 28 | */ 29 | 30 | /** Error-free addition of two floating point numbers. 31 | * See Shewchuk page 8. Note that output order is swapped! */ 32 | 33 | function twoSum(a: number, b: number, sum: number[]) { 34 | const estimate = a + b; 35 | const b2 = estimate - a; 36 | const a2 = estimate - b2; 37 | 38 | sum[0] = (a - a2) + (b - b2); 39 | sum[1] = estimate; 40 | 41 | return(sum); 42 | } 43 | 44 | /** Error-free product of two floating point numbers. 45 | * Store approximate result in global variable tempProduct. 46 | * See Shewchuk page 20. 47 | * 48 | * @return Rounding error. */ 49 | 50 | function twoProduct(a: number, b: number) { 51 | tempProduct = a * b; 52 | 53 | const a2 = a * dekkerSplitter; 54 | const aHi = a2 - (a2 - a); 55 | const aLo = a - aHi; 56 | 57 | const b2 = b * dekkerSplitter; 58 | const bHi = b2 - (b2 - b); 59 | const bLo = b - bHi; 60 | 61 | return(aLo * bLo - (tempProduct - aHi * bHi - aLo * bHi - aHi * bLo)); 62 | } 63 | 64 | /** Arbitrary precision floating point number. Based on a multiple-component 65 | * expansion format and error free transformations. 66 | * 67 | * Maximum exponent is the same as for plain JavaScript numbers, 68 | * least significant representable binary digit is 2^-1074. */ 69 | 70 | export class BigFloat53 implements BigFloatBase { 71 | 72 | /** @param value Initial value, a plain JavaScript floating point number 73 | * (IEEE 754 double precision). */ 74 | 75 | constructor(value?: BigFloat53 | number | string, base?: number) { 76 | if(value) this.setValue(value, base); 77 | } 78 | 79 | clone() { 80 | return(new BigFloat53().setBig(this)); 81 | } 82 | 83 | /** Set value to zero. 84 | * 85 | * @return This object, for chaining. */ 86 | 87 | setZero() { 88 | this.len = 0; 89 | 90 | return(this); 91 | } 92 | 93 | setValue(other: BigFloat53 | number | string, base?: number) { 94 | if(typeof(other) == 'number') { 95 | return(this.setNumber(other)); 96 | } 97 | if(other instanceof BigFloat53) { 98 | return(this.setBig(other)); 99 | } 100 | 101 | return(this.setString(other.toString(), base || 10)); 102 | } 103 | 104 | private setBig(other: BigFloat53) { 105 | const len = other.len; 106 | 107 | this.len = len; 108 | 109 | for(let pos = 0; pos < len; ++pos) { 110 | this.limbList[pos] = other.limbList[pos]; 111 | } 112 | 113 | return(this); 114 | } 115 | 116 | /** Set value to a plain JavaScript floating point number 117 | * (IEEE 754 double precision). 118 | * 119 | * @param value New value. 120 | * @return This object, for chaining. */ 121 | 122 | private setNumber(value: number) { 123 | this.limbList[0] = value; 124 | this.len = value && 1; 125 | 126 | return(this); 127 | } 128 | 129 | private setString(value: string, base: number) { 130 | temp32[0].setValue(value, base); 131 | 132 | this.len = temp32[0].getExpansion(this.limbList); 133 | this.normalize(); 134 | 135 | return(this); 136 | } 137 | 138 | /** Set value to the sum of two JavaScript numbers. 139 | * 140 | * @param a Augend. 141 | * @param b Addend. 142 | * @return This object, for chaining. */ 143 | 144 | setSum(a: number, b: number) { 145 | this.len = 2; 146 | twoSum(a, b, this.limbList); 147 | return(this); 148 | } 149 | 150 | /** Set value to the product of two JavaScript numbers. 151 | * @param a Multiplicand. 152 | * @param b Multiplier. 153 | * @return This object, for chaining. */ 154 | 155 | setProduct(a: number, b: number) { 156 | this.len = 2; 157 | this.limbList[0] = twoProduct(a, b); 158 | this.limbList[1] = tempProduct; 159 | return(this); 160 | } 161 | 162 | /** See Compress from Shewchuk page 25. */ 163 | 164 | // TODO: Test. 165 | normalize() { 166 | const limbList = this.limbList; 167 | let len = this.len; 168 | let limb: number; 169 | 170 | if(len) { 171 | let a = len - 1; 172 | let b = len - 1; 173 | 174 | let q = limbList[a]; 175 | let err: number; 176 | 177 | while(a) { 178 | limb = limbList[--a]; 179 | err = q; 180 | q += limb; 181 | 182 | err = limb - (q - err); 183 | 184 | limbList[b] = q; 185 | b -= err && 1; 186 | q = err || q; 187 | } 188 | 189 | limbList[b] = q; 190 | 191 | while(++b < len) { 192 | limb = limbList[b]; 193 | err = q; 194 | q += limb; 195 | 196 | err -= q - limb; 197 | 198 | limbList[a] = err; 199 | a += err && 1; 200 | } 201 | 202 | limbList[a] = q; 203 | this.len = a + (q && 1); 204 | } 205 | 206 | return(this); 207 | } 208 | 209 | /** Multiply this arbitrary precision float by a number. 210 | * See Scale-Expansion from Shewchuk page 21. 211 | * 212 | * @param b Multiplier, a JavaScript floating point number. 213 | * @param product Arbitrary precision float to overwrite with result. 214 | * @return Modified product object. */ 215 | 216 | mulSmall(b: number, product: BigFloat53) { 217 | const limbList = this.limbList; 218 | const productLimbs = product.limbList; 219 | const count = this.len; 220 | let t1: number, t2: number, t3: number; 221 | let srcPos = 0, dstPos = 0; 222 | 223 | /** Write output limb and move to next, unless a zero was written. */ 224 | 225 | function writeLimb(limb: number) { 226 | productLimbs[dstPos] = limb; 227 | dstPos += limb && 1; 228 | } 229 | 230 | writeLimb(twoProduct(limbList[srcPos++], b)); 231 | let q = tempProduct; 232 | 233 | while(srcPos < count) { 234 | t1 = twoProduct(limbList[srcPos++], b); 235 | t2 = q + t1; 236 | t3 = t2 - q; 237 | 238 | writeLimb(q - (t2 - t3) + (t1 - t3)); 239 | q = tempProduct + t2; 240 | writeLimb(t2 - (q - tempProduct)); 241 | } 242 | 243 | productLimbs[dstPos] = q; 244 | product.len = dstPos + (q && 1); 245 | 246 | return(product); 247 | } 248 | 249 | /** Multiply this by an arbitrary precision multiplier. 250 | * Pass all components of the multiplier to mulSmall and sum the products. 251 | * 252 | * @param multiplier Number or arbitrary precision float. 253 | * @param product Arbitrary precision float to overwrite with result. 254 | * @return Modified product object. */ 255 | 256 | private mulBig(multiplier: BigFloat53, product: BigFloat53) { 257 | const limbList = multiplier.limbList; 258 | let pos = multiplier.len; 259 | 260 | if(!pos) return(product.setZero()); 261 | 262 | --pos; 263 | this.mulSmall(limbList[pos], pos ? temp53[pos & 1] : product); 264 | 265 | while(pos) { 266 | --pos; 267 | this.mulSmall(limbList[pos], product).addBig(temp53[~pos & 1], 1, pos ? temp53[pos & 1] : product); 268 | } 269 | 270 | return(product); 271 | } 272 | 273 | /** Multiply number or arbitrary precision float with this one 274 | * and store result in another BigFloat53. 275 | * 276 | * @param multiplier Number or arbitrary precision float. 277 | * @param product Arbitrary precision float to overwrite with result. 278 | * If omitted, a new one is allocated. 279 | * @return Modified product object. */ 280 | 281 | mul(multiplier: number | BigFloat53, product?: BigFloat53) { 282 | product = product || new BigFloat53(); 283 | 284 | if(typeof(multiplier) == 'number') { 285 | return(this.mulSmall(multiplier, product)); 286 | } 287 | 288 | if(product == this) throw(new Error('Cannot multiply in place')); 289 | 290 | return(this.mulBig(multiplier, product)); 291 | } 292 | 293 | // TODO 294 | // absDeltaFrom(other: number | BigFloat53) { return(0); } 295 | 296 | cmp: (other: number | BigFloat53) => number; 297 | 298 | isZero() { 299 | const limbList = this.limbList; 300 | let pos = this.len; 301 | 302 | while(pos--) { 303 | if(limbList[pos]) return(false); 304 | } 305 | 306 | return(true); 307 | } 308 | 309 | getSign() { 310 | let t = this.len; 311 | 312 | return(t && (t = this.limbList[t - 1]) && (t > 0 ? 1 : -1)); 313 | } 314 | 315 | /** Return an arbitrary number with sign matching the result of this - other. */ 316 | 317 | // TODO: Test. 318 | deltaFrom(other: number | BigFloat53) { 319 | let t = this.len; 320 | let sign = this.getSign(); 321 | let diff = sign; 322 | 323 | if(typeof(other) != 'number') { 324 | t = other.len; 325 | diff -= t && (t = other.limbList[t - 1]) && (t > 0 ? 1 : -1); 326 | if(diff || !sign) return(diff); 327 | 328 | this.addBig(other, -1, temp53[0]); 329 | } else { 330 | diff -= other && (other > 0 ? 1 : -1); 331 | if(diff || !sign) return(diff); 332 | 333 | this.addSmall(-other, temp53[0]); 334 | } 335 | 336 | t = temp53[0].len; 337 | return(t && temp53[0].limbList[t - 1]); 338 | } 339 | 340 | /** Add a number to this arbitrary precision float. 341 | * See Grow-Expansion from Shewchuk page 10. 342 | * 343 | * @param b JavaScript floating point number to add. 344 | * @param sum Arbitrary precision float to overwrite with result. 345 | * @return Modified sum object. */ 346 | 347 | addSmall(b: number, sum: BigFloat53) { 348 | const limbList = this.limbList; 349 | const sumLimbs = sum.limbList; 350 | const count = this.len; 351 | let estimate: number; 352 | let a: number, a2: number, b2: number, err: number; 353 | let srcPos = 0, dstPos = 0; 354 | 355 | while(srcPos < count) { 356 | a = limbList[srcPos++]; 357 | 358 | estimate = a + b; 359 | b2 = estimate - a; 360 | a -= estimate - b2; 361 | err = a + (b - b2); 362 | 363 | sumLimbs[dstPos] = err; 364 | dstPos += err && 1; 365 | b = estimate; 366 | } 367 | 368 | sumLimbs[dstPos] = b; 369 | sum.len = dstPos + (b && 1); 370 | 371 | return(sum); 372 | } 373 | 374 | /** Add another arbitrary precision float (multiplied by sign) to this one. 375 | * See Fast-Expansion-Sum from Shewchuk page 13. 376 | * 377 | * @param sign Multiplier for negating addend to implement subtraction. 378 | * @param sum Arbitrary precision float to overwrite with result. 379 | * @return Modified sum object. */ 380 | 381 | private addBig(addend: BigFloat53, sign: -1 | 1, sum: BigFloat53) { 382 | const augendLimbs = this.limbList; 383 | const addendLimbs = addend.limbList; 384 | const sumLimbs = sum.limbList; 385 | let count = this.len + addend.len; 386 | let nextAugendPos = 0; 387 | let nextAddendPos = 0; 388 | let nextSumPos = 0; 389 | /** Latest limb of augend. */ 390 | let a = augendLimbs[nextAugendPos++]; 391 | /** Latest limb of addend. */ 392 | let b = addendLimbs[nextAddendPos++] * sign; 393 | /** Magnitude of latest augend limb. */ 394 | let a2 = a < 0 ? -a : a; 395 | /** Magnitude of latest addend limb. */ 396 | let b2 = b < 0 ? -b : b; 397 | let nextLimb: number, nextLimb2: number, prevLimb: number; 398 | let err: number; 399 | 400 | if(!count) return(sum.setZero()); 401 | 402 | // Append sentinel limbs to avoid testing for end of array. 403 | augendLimbs[this.len] = Infinity; 404 | addendLimbs[addend.len] = Infinity; 405 | 406 | /** Get next smallest limb from either augend or addend. 407 | * This avoids merging the two limb lists. */ 408 | 409 | function getNextLimb() { 410 | let result: number; 411 | 412 | if(a2 < b2) { 413 | result = a; 414 | a = augendLimbs[nextAugendPos++]; 415 | a2 = a < 0 ? -a : a; 416 | } else { 417 | result = b; 418 | b = addendLimbs[nextAddendPos++] * sign; 419 | b2 = b < 0 ? -b : b; 420 | } 421 | 422 | return(result); 423 | } 424 | 425 | let limb = getNextLimb(); 426 | 427 | while(--count) { 428 | nextLimb = getNextLimb(); 429 | prevLimb = limb; 430 | 431 | limb += nextLimb; 432 | nextLimb2 = limb - prevLimb; 433 | err = (prevLimb - (limb - nextLimb2)) + (nextLimb - nextLimb2); 434 | 435 | sumLimbs[nextSumPos] = err; 436 | nextSumPos += err && 1; 437 | } 438 | 439 | sumLimbs[nextSumPos] = limb; 440 | sum.len = nextSumPos + (limb && 1); 441 | 442 | return(sum); 443 | } 444 | 445 | private addSub(addend: number | BigFloat53, sign: -1 | 1, result?: BigFloat53) { 446 | result = result || new BigFloat53(); 447 | 448 | if(typeof(addend) == 'number') return(this.addSmall(sign * addend, result)); 449 | 450 | return(this.addBig(addend, sign, result)); 451 | } 452 | 453 | /** Add number or arbitrary precision float to this one 454 | * and store result in another BigFloat53. 455 | * 456 | * @param addend Number or arbitrary precision float. 457 | * @param sum Arbitrary precision float to overwrite with result. 458 | * If omitted, a new one is allocated. 459 | * @return Modified sum object. */ 460 | 461 | add(addend: number | BigFloat53, sum?: BigFloat53) { 462 | return(this.addSub(addend, 1, sum)); 463 | } 464 | 465 | /** Subtract number or arbitrary precision float from this one 466 | * and store result in another BigFloat53. 467 | * 468 | * @param subtrahend Number or arbitrary precision float. 469 | * @param difference Arbitrary precision float to overwrite with result. 470 | * If omitted, a new one is allocated. 471 | * @return Modified difference object. */ 472 | 473 | sub(subtrahend: number | BigFloat53, difference?: BigFloat53) { 474 | return(this.addSub(subtrahend, -1, difference)); 475 | } 476 | 477 | /** Round towards zero, to (at least) given number of base 2^53 fractional digits. */ 478 | 479 | truncate(fractionLimbCount: number) { 480 | this.normalize(); 481 | 482 | const limbList = this.limbList; 483 | let len = this.len; 484 | 485 | // Use binary search to find last |limb| < 1. 486 | 487 | let lo = 0; 488 | let hi = len; 489 | let mid = 0; 490 | let limb = 0; 491 | 492 | while(lo < hi) { 493 | mid = (lo + hi) >> 1; 494 | limb = limbList[mid]; 495 | 496 | if(limb > -1 && limb < 1) { 497 | lo = mid + 1; 498 | } else { 499 | hi = mid; 500 | } 501 | } 502 | 503 | if(mid && (limb <= -1 || limb >= 1)) { 504 | limb = limbList[--mid]; 505 | } 506 | 507 | // Slice off limbs before and including it, 508 | // except the fractionLimbCount last ones. 509 | 510 | mid -= fractionLimbCount - 1; 511 | 512 | if(mid > 0) { 513 | this.len -= mid; 514 | len = this.len; 515 | 516 | for(let pos = 0; pos < len; ++pos) { 517 | limbList[pos] = limbList[pos + mid]; 518 | } 519 | } 520 | 521 | return(this); 522 | } 523 | 524 | round(decimalCount: number) { 525 | return(this.truncate(1 + ~~(decimalCount * limbsPerDigit53))); 526 | } 527 | 528 | valueOf() { 529 | const limbList = this.limbList; 530 | const len = this.len; 531 | let result = 0; 532 | 533 | for(let pos = 0; pos < len; ++pos) { 534 | result += limbList[pos]; 535 | } 536 | 537 | return(result); 538 | } 539 | 540 | /** Convert to string in any even base supported by Number.toString. 541 | * @return String in lower case. */ 542 | 543 | toString(base?: number) { 544 | const limbList = this.limbList; 545 | let pos = this.len; 546 | 547 | temp32[pos & 1].setZero(); 548 | 549 | while(pos--) { 550 | temp32[~pos & 1].add(limbList[pos], temp32[pos & 1]); 551 | } 552 | 553 | return(temp32[~pos & 1].toString(base)); 554 | } 555 | 556 | /** List of components ordered by increasing exponent. */ 557 | private limbList: number[] = []; 558 | private len: number; 559 | 560 | } 561 | 562 | BigFloat53.prototype.cmp = BigFloat53.prototype.deltaFrom; 563 | 564 | /** Latest approximate product from twoProduct. */ 565 | let tempProduct = 0; 566 | 567 | /** Temporary values for internal calculations. */ 568 | const temp32 = [ new BigFloat32(), new BigFloat32() ]; 569 | 570 | /** Temporary values for internal calculations. */ 571 | const temp53 = [ new BigFloat53(), new BigFloat53() ]; 572 | -------------------------------------------------------------------------------- /src/BigFloatBase.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2018- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | export interface BigFloatBase { 5 | clone(): Type; 6 | setZero(): Type; 7 | setValue(value: Type | number | string, base?: number): Type; 8 | mul(multiplier: number | Type, product?: Type): Type; 9 | // absDeltaFrom(other: number | Type): Type; 10 | cmp(other: number | Type): number; 11 | 12 | isZero(): boolean; 13 | getSign(): number; 14 | deltaFrom(other: number | Type): number; 15 | add(addend: number | Type, sum?: Type): Type; 16 | sub(subtrahend: number | Type, difference?: Type): Type; 17 | truncate(fractionLimbCount: number): Type; 18 | round(decimalCount: number): Type; 19 | valueOf(): number; 20 | toString(base: number): string; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | export { BigFloat32 } from './BigFloat32'; 5 | export { BigFloat53 } from './BigFloat53'; 6 | export { BigComplex32, BigComplex53 } from './BigComplex'; 7 | export { trimNumber, numberToString } from './util'; 8 | -------------------------------------------------------------------------------- /src/ts-esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "module": "es6", 5 | "outDir": "../dist/esm" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationDir": "../dist/esm", 6 | "lib": [ "es5" ], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noEmitHelpers": true, 10 | "noImplicitAny": true, 11 | "noImplicitThis": true, 12 | "outDir": "../dist/cjs", 13 | "removeComments": false, 14 | "sourceMap": false, 15 | "strictFunctionTypes": true, 16 | "strictNullChecks": true, 17 | "target": "es5", 18 | "types": [] 19 | }, 20 | "files": [ 21 | "index.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | // This file is part of bigfloat, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { BaseInfo32, limbSize32 } from './BaseInfo32'; 5 | 6 | /** Remove leading and trailing insignificant zero digits. */ 7 | 8 | export function trimNumber(str: string) { 9 | return(str 10 | .replace(/^(-?)0+([1-9a-z]|0(\.|$))/, '$1$2') 11 | .replace(/(\.|(\.[0-9a-z]*[1-9a-z]))0+$/, '$2') 12 | ); 13 | } 14 | 15 | /** Output EXACT value of an IEEE 754 double in any base supported by Number.toString. 16 | * Exponent must be between -2 and 61, and last 3 bits of mantissa must be 0. 17 | * Useful for debugging. */ 18 | 19 | export function numberToString(dbl: number, base = 10) { 20 | let { pad, limbBase } = BaseInfo32.init(base); 21 | let sign = ''; 22 | let out = ''; 23 | let limb: number; 24 | let limbStr: string; 25 | 26 | if(isNaN(dbl)) return('NaN'); 27 | 28 | // For negative numbers, output sign and get absolute value. 29 | if(dbl < 0) { 30 | sign = '-'; 31 | dbl = -dbl; 32 | } 33 | 34 | if(!isFinite(dbl)) return(sign + 'Inf'); 35 | 36 | if(dbl < 1) { 37 | out += '0'; 38 | } else { 39 | let iPart = Math.floor(dbl); 40 | dbl -= iPart; 41 | 42 | while(iPart) { 43 | // Extract groups of digits starting from the least significant. 44 | limb = iPart % limbBase; 45 | iPart = (iPart - limb) / limbBase; 46 | limbStr = limb.toString(base); 47 | 48 | // Prepend digits to result. 49 | out = limbStr + out; 50 | 51 | // If more limbs remain, pad with zeroes to group length. 52 | if(iPart) out = pad.substr(limbStr.length) + out; 53 | } 54 | } 55 | 56 | // Is there a fractional part remaining? 57 | if(dbl > 0) { 58 | out += '.'; 59 | 60 | if(limbBase != limbSize32) { 61 | limbBase = base; 62 | pad = ''; 63 | } 64 | 65 | while(dbl) { 66 | // Extract groups of digits starting from the most significant. 67 | dbl *= limbBase; 68 | limb = dbl >>> 0; 69 | dbl -= limb; 70 | limbStr = limb.toString(base); 71 | 72 | // Append digits to result and pad with zeroes to group length. 73 | out += pad.substr(limbStr.length) + limbStr; 74 | } 75 | } 76 | 77 | // Remove trailing zeroes. 78 | return(sign + out.replace(/(\.[0-9a-z]*[1-9a-z])0+$/, '$1')); 79 | } 80 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.d.ts 3 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import { BigFloat32, trimNumber, numberToString } from '..'; 2 | import * as childProcess from 'child_process'; 3 | import * as fs from 'fs'; 4 | 5 | const buf = new ArrayBuffer(8); 6 | const charView = new Uint8Array(buf); 7 | const shortView = new Uint16Array(buf); 8 | const doubleView = new Float64Array(buf); 9 | 10 | function randDouble() { 11 | for(let i = 0; i < 4; ++i) { 12 | shortView[i] = ~~(Math.random() * 65536); 13 | } 14 | 15 | exp = (Math.random() * 64) - 2; 16 | 17 | // Set exponent. 18 | charView[7] = (charView[7] & 0x80) | ((exp + 1023) >> 4); 19 | charView[6] = (charView[6] & 0x0f) | ((exp + 1023) << 4); 20 | 21 | // Clear last 3 bits of mantissa. 22 | charView[0] &= 0xf8; 23 | 24 | return(doubleView[0]); 25 | } 26 | 27 | function debug(dbl: number) { 28 | const big = new BigFloat32(dbl); 29 | 30 | const b10a = numberToString(dbl); 31 | const b10b = big.toString(); 32 | const b16a = numberToString(dbl, 16); 33 | const b16b = big.toString(16); 34 | 35 | if(b10a != b10b || b16a != b16b) { 36 | console.log('ERROR'); 37 | 38 | const buf = new ArrayBuffer(8); 39 | const charView = new Uint8Array(buf); 40 | const doubleView = new Float64Array(buf); 41 | 42 | doubleView[0] = dbl; 43 | 44 | console.log('hex = ' + Array.prototype.map.call(charView, (x: number) => (x < 16 ? '0' : '') + x.toString(16)).join('')); 45 | console.log('exp = ' + (Math.log(Math.abs(dbl)) / Math.LN2)); 46 | 47 | console.log(b10a); 48 | console.log(b10b); 49 | console.log(b16a); 50 | console.log(b16b); 51 | } 52 | } 53 | 54 | let count = 0; 55 | let exp = 0; 56 | 57 | console.log('Fuzz-testing conversion number -> BigFloat -> string...'); 58 | 59 | for(let i = 0; i < 100000; ++i) { 60 | debug(randDouble()); 61 | } 62 | 63 | interface TestSpec { 64 | expr: string; 65 | libResult: string; 66 | }; 67 | 68 | type Test = () => TestSpec; 69 | 70 | const bc = childProcess.spawn('bc'); 71 | 72 | let testSpec: TestSpec; 73 | let a = new BigFloat32(); 74 | let b = new BigFloat32(); 75 | let t = new BigFloat32(); 76 | 77 | let total = 0; 78 | let testNum = 0; 79 | let bcResult = ''; 80 | 81 | bc.stdout.on('data', (data: string) => { 82 | bcResult += data.toString(); 83 | 84 | // If BC output didn't end in a line break or had a continuation backslash 85 | // before it, then more output is still coming. 86 | if(!bcResult.match(/[^\\]\n$/)) return; 87 | 88 | bcResult = trimNumber(bcResult.replace(/\\\n/g, '').trim().toLowerCase().replace(/^(-?)\./, '$10.')); 89 | 90 | if(testSpec.libResult != bcResult) { 91 | console.log(testSpec.expr); 92 | console.log('BigFloat: ' + testSpec.libResult); 93 | console.log('bc: ' + bcResult); 94 | console.log(''); 95 | } 96 | 97 | test(); 98 | }) 99 | 100 | function test() { 101 | ++total; 102 | if(total % 1000 == 0) console.log(total); 103 | 104 | if(total >= 10000) { 105 | ++testNum; 106 | total = 0; 107 | } 108 | 109 | if(testList[testNum]) { 110 | testSpec = testList[testNum](); 111 | 112 | bcResult = ''; 113 | bc.stdin.write(testSpec.expr); 114 | } else bc.stdin.end(); 115 | } 116 | 117 | function sign(x: number) { 118 | return(x > 0 ? 1 : x < 0 ? -1 : 0); 119 | } 120 | 121 | let testList: Test[] = [ 122 | () => { 123 | a.setValue(randDouble()); 124 | b.setValue(randDouble()); 125 | 126 | return({ 127 | expr: a.toString(10) + ' * ' + b.toString(10) + '\n', 128 | libResult: a.mul(b, t).toString(10) 129 | }); 130 | }, 131 | () => { 132 | a.setValue(randDouble()); 133 | b.setValue(randDouble()); 134 | 135 | return({ 136 | expr: 'sign(' + a.toString(10) + ' - ' + b.toString(10) + ')\n', 137 | libResult: '' + sign(a.deltaFrom(b)) 138 | }); 139 | }, 140 | () => { 141 | a.setValue(randDouble()); 142 | b.setValue(randDouble()); 143 | 144 | return({ 145 | expr: a.toString(10) + ' + ' + b.toString(10) + '\n', 146 | libResult: a.add(b, t).toString(10) 147 | }); 148 | }, 149 | () => { 150 | a.setValue(randDouble()); 151 | b.setValue(randDouble()); 152 | 153 | return({ 154 | expr: a.toString(10) + ' - ' + b.toString(10) + '\n', 155 | libResult: a.sub(b, t).toString(10) 156 | }); 157 | } 158 | ] 159 | 160 | console.log('Fuzz-testing...'); 161 | 162 | bc.stdin.write('define sign(x) {if(x>0) {return(1);} else if(x<0) {return(-1);} else return(0);}'); 163 | bc.stdin.write('scale=1000\n'); 164 | 165 | test(); 166 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "lib": [ "es5", "es2015.collection", "es2015.symbol.wellknown" ], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noImplicitAny": true, 9 | "noImplicitThis": true, 10 | "removeComments": false, 11 | "sourceMap": false, 12 | "strictNullChecks": true, 13 | "target": "es5" 14 | }, 15 | "files": [ 16 | "test.ts" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------