Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active September 9, 2020 03:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/f78f82906c3847bcbf98e9343c376c7a to your computer and use it in GitHub Desktop.
Save dfkaye/f78f82906c3847bcbf98e9343c376c7a to your computer and use it in GitHub Desktop.
safe-math-avg.js ~ safer numeric average computation
/*
* Update 17 August 2020:
* Now part of safe math blog post at https://dfkaye.com/posts/2020/08/17/safer-math-operations-in-javascript-using-tdd/
* Test suite at https://dfkaye.com/demos/safe-math-test-suite/
*/
// 14 August 2020
// Returns the average of a list of functionally numeric values.
// Params may be a single array of values, or any number of value params.
// Assumes booleans and numeric strings are functionally numeric, but ignores other non-numerics.
// Includes safer math helper against 0.1 + 0.2 (0.30000000000000004) from gist at
// https://gist.github.com/dfkaye/c2210ceb0f813dda498d22776f98d48a
function avg() {
var args = Array.isArray(arguments[0])
? arguments[0]
: arguments;
var size = 0;
return [].slice.call(args).reduce(function(sum, next) {
// Expand values to { left, right, by }.
var e = expand(+sum, +next);
return +e.right === +e.right
// If next is a number, add the values and increment the set size.
? (size += 1, (e.left + e.right) / e.by)
// Else just return current sum.
: sum
}, size) / size;
}
/*
* eExpand() is a helper carved from safer math ops gist at
* https://gist.github.com/dfkaye/c2210ceb0f813dda498d22776f98d48a
*/
function expand(a, b) {
// remove formatting commas
var reCommas = /[\,]/g
var left = (a).toString().replace(reCommas, '');
var right = (b).toString().replace(reCommas, '');
// expand to integer values based on largest mantissa length
var reDecimal = /[\.]/;
var ml = reDecimal.test(left) && left.split('.')[1].length;
var mr = reDecimal.test(right) && right.split('.')[1].length;
var pow = ml > mr ? ml : mr;
var by = Math.pow(10, pow);
// left & right number pair, plus the expansion factor.
// The multiplication operator, *, coerces non-numerics to their equivalent,
// e.g., {} => NaN, true => 1, [4] => '4' => 4
return { left: left * by, right: right * by, by };
}
/* Test it out... */
console.log( avg(1, 2, 3, 4) );
// 2.5
console.log( avg([1, 2, , 3, 4]) );
// 2.5
console.log( avg(1, 2, 3, 'bonk', 4) );
// 2.5
console.log( avg([1, 2, 'bonk' , 3, 4]) );
// 2.5
console.log( avg(1, 2, '3.0', 'bonk', 4) );
// 2.5
console.log( avg([1, 2, 'bonk' , '3.0', 4]) );
// 2.5
// false evaluates to 0
console.log( avg(1, 2, 3, false, 4) );
// 2.5
// true evaluates to 1
console.log( avg([1, true, 2, 3, 4]) );
// 2.5
/* 0.1 + 0.2 */
console.log( avg(0.1, 0.2) );
// 0.15
console.log( avg(0.01, 0.02) );
// 0.015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment