├── .gitignore ├── .travis.yml ├── Readme.md ├── benchmark.js ├── bower.json ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | - "0.8" 6 | - "0.6" 7 | 8 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Add 2 | 3 | [![Build Status](https://travis-ci.org/ben-ng/add.svg?branch=master)](https://travis-ci.org/ben-ng/add) 4 | 5 | [![browser support](https://ci.testling.com/ben-ng/add.png) 6 | ](https://ci.testling.com/ben-ng/add) 7 | 8 | A cross-browser, numerically stable way to add floats in Javascript. Produces a faithful rounding of the sum (the result is an immediate floating-point neighbor of the true value). 9 | 10 | Algorithm: Rump-Ogita-Oishi 11 | 12 | ## Usage 13 | 14 | ```javascript 15 | var add = require('add') 16 | , evil = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7] 17 | , dumbsum = function (a,b) { return a+b } 18 | 19 | console.log(evil.reduce(dumbsum)) => 15.299999999999999 20 | 21 | console.log(add(evil)) => 15.3 22 | ``` 23 | 24 | ## Performance 25 | 26 | The performance benchmark tesll you how much slower `add` is compared to dumb addition. Run it using: 27 | 28 | ```bash 29 | $ npm run benchmark 30 | ``` 31 | 32 | Here are some results (OS X 10.9.4, 2 GHz Core i7, 8GB DDR3 1600Mhz RAM): 33 | 34 | ```bash 35 | add-precise x 1,400,712 ops/sec ±3.31% (89 runs sampled) 36 | add-dumb x 24,268,034 ops/sec ±3.96% (80 runs sampled) 37 | native x 94,957,251 ops/sec ±2.94% (85 runs sampled) 38 | native is ~67.8 times faster than add-precise 39 | ``` 40 | 41 | ## License 42 | 43 | The MIT License (MIT) 44 | 45 | Copyright (c) 2014 Ben Ng 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a copy 48 | of this software and associated documentation files (the "Software"), to deal 49 | in the Software without restriction, including without limitation the rights 50 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 51 | copies of the Software, and to permit persons to whom the Software is 52 | furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in 55 | all copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 63 | THE SOFTWARE. 64 | 65 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'), 2 | add = require('./'); 3 | 4 | 5 | var testData = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7] 6 | 7 | 8 | var addCustom = require('./'); 9 | 10 | 11 | var addNative = function(p) { 12 | var result = 0; 13 | for (var i=0; i" 8 | ], 9 | "description": "A cross-browser, numerically stable algorithm to add floats accurately", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "numerical", 17 | "stable", 18 | "faithful", 19 | "rounding", 20 | "float", 21 | "error", 22 | "propagation", 23 | "summation", 24 | "accumulate", 25 | "addition", 26 | "numerical", 27 | "analysis" 28 | ], 29 | "license": "MIT", 30 | "ignore": [ 31 | "**/.*", 32 | "node_modules", 33 | "bower_components", 34 | "test.js", 35 | "benchmark.js" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | "use strict"; 3 | 4 | // AMD 5 | if (typeof define === 'function' && define.amd) { 6 | define([], factory); 7 | } 8 | // CommonJS 9 | else if (typeof exports === 'object') { 10 | module.exports = factory(); 11 | } 12 | // Browser 13 | else { 14 | root.add = factory(); 15 | } 16 | })(this, function () { 17 | "use strict"; 18 | 19 | // The minimum machine rounding error 20 | var Epsilon = Math.pow(2, -53) 21 | , EpsilonReciprocal = (1 / Epsilon) 22 | /// The smallest positive number that can be represented 23 | , Eta = Math.pow(2, -1074) 24 | // limitB is a constant used in the transform function 25 | , limitB = 0.5 * EpsilonReciprocal * Eta 26 | 27 | /** 28 | * S. M. RUMP, T. OGITA AND S. OISHI 29 | * http://www.ti3.tu-harburg.de/paper/rump/RuOgOi07I.pdf 30 | */ 31 | 32 | // Page 8 33 | // x is result, y is error 34 | // third is so the array is allocated for 4 spaces 35 | // it speeds up transform 36 | function fastTwoSum(a, b) { 37 | var x = a + b 38 | , q = x - a 39 | , y = b - q 40 | 41 | return [x, y, null] 42 | } 43 | 44 | // Page 12 45 | // p = q + p' 46 | // sigma is a power of 2 greater than or equal to |p| 47 | function extractScalar(sigma, p) { 48 | var q = (sigma + p) - sigma 49 | , pPrime = p - q 50 | 51 | return [q, pPrime] 52 | } 53 | 54 | // Page 12 55 | function extractVector(sigma, p) { 56 | var tau = 0.0 57 | , extracted 58 | , i = 0 59 | , ii = p.length 60 | , pPrime = new Array(ii) 61 | 62 | for(; i best) { 91 | best = arr[i] 92 | } 93 | } 94 | 95 | return best 96 | } 97 | 98 | function transform (p) { 99 | var mu = maxAbs(p) 100 | , M 101 | , sigmaPrime 102 | , tPrime 103 | , t 104 | , tau 105 | , sigma 106 | , extracted 107 | , res 108 | 109 | // Not part of the original paper, here for optimization 110 | , temp 111 | , bigPow 112 | , limitA 113 | , twoToTheM 114 | 115 | if(mu === 0) { 116 | return [0, 0, p, 0] 117 | } 118 | 119 | M = nextPowerTwo(p.length + 2) 120 | twoToTheM = Math.pow(2, M) 121 | bigPow = 2 * twoToTheM // equiv to Math.pow(2, 2 * M), faster 122 | sigmaPrime = twoToTheM * nextPowerTwo(mu) 123 | tPrime = 0 124 | 125 | do { 126 | t = tPrime 127 | sigma = sigmaPrime 128 | extracted = extractVector(sigma, p) 129 | tau = extracted[0] 130 | tPrime = t + tau 131 | p = extracted[1] 132 | 133 | if(tPrime === 0) { 134 | return transform(p) 135 | } 136 | 137 | temp = Epsilon * sigma 138 | sigmaPrime = twoToTheM * temp 139 | limitA = bigPow * temp 140 | } 141 | while( Math.abs(tPrime) < limitA && sigma > limitB ) 142 | 143 | // res already allocated for 4 144 | res = fastTwoSum(t, tau) 145 | res[2] = p 146 | 147 | return res 148 | } 149 | 150 | function dumbSum(p) { 151 | var i, ii, sum = 0.0 152 | for(i=0, ii=p.length; i", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/ben-ng/add/issues" 48 | }, 49 | "homepage": "https://github.com/ben-ng/add" 50 | } 51 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests for numeric stability 3 | */ 4 | var algorithm = require('./') 5 | , test = require('tape') 6 | , badVector 7 | 8 | badVector = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7] 9 | 10 | test('fastSum', function (t) { 11 | t.plan(1); 12 | 13 | t.deepEqual(algorithm.fastTwoSum(1/3, 1/6) 14 | , [0.5, -2.7755575615628914e-17, null] 15 | , 'Result and error should have been returned') 16 | }); 17 | 18 | test('nextPowerTwo', function (t) { 19 | t.plan(1) 20 | 21 | t.equal(algorithm.nextPowerTwo(1534) 22 | , 2048 23 | , 'Should be Math.pow(2, Math.ceil(algorithm.logBase2(Math.abs(1534))))') 24 | }) 25 | 26 | test('accumulate', function (t) { 27 | t.plan(5) 28 | 29 | 30 | t.equal(algorithm([1,2,3,4]), 10, 'Integer sum should work') 31 | 32 | 33 | t.equal(algorithm.dumbSum(badVector), 15.299999999999999, 'Inaccurate summation using naive method') 34 | 35 | t.equal(algorithm(badVector), 15.3, 'Rump-Ogita-Oishi summation of insidious sum') 36 | 37 | t.equal(algorithm([0, 0, 0]), 0, 'Rump-Ogita-Oishi summation of zero array') 38 | 39 | t.equal(algorithm([]), 0, 'Rump-Ogita-Oishi summation of empty array') 40 | }) 41 | 42 | --------------------------------------------------------------------------------