Skip to content

Instantly share code, notes, and snippets.

@niklas-r
Last active August 29, 2015 13:59
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 niklas-r/10456375 to your computer and use it in GitHub Desktop.
Save niklas-r/10456375 to your computer and use it in GitHub Desktop.
Round list of floats to list of integers. The sum of the integers will be the same as the floats combined and then rounded.
function roundFloats(listOfFloats) {
var tempArr,
arraySum,
lowerSum,
largestFraction,
i;
// lowerSum will be the sum of all floats Math.floored
lowerSum = 0;
tempArr = listOfFloats.map(function (flp, i) {
var item;
// array items with higher individual difference are
// more likely to get an extra integer from the
// total differene sum.
item = {
result: Math.floor(flp),
difference: flp - Math.floor(flp),
index: i
};
lowerSum += item.difference;
return item;
});
// we need to know the largest fraction size because
// we have to multiply each array item by that
// in order to avoid weird float point bugs
largestFraction = listOfFloats.reduce(function (memo, fl) {
var fractionSize;
fractionSize = (fl + '').split('.')[1].length;
return (fractionSize > memo) ? fractionSize : memo;
}, 0);
// get sum of array
arraySum =
listOfFloats.reduce(function (memo, fl) {
return memo + (fl * largestFraction);
}, 0) / largestFraction;
// This does some magic. JavaScript uses the IEEE 754 standard for
// floating point values which causes all kinds of fun bugs.
// To mitigate this we set the lowerSum to only have 2 decimals at
// the start. toFixed returns a string so we later must parse it as
// a float again. In the end we run a Math.floor on the sum to eliminate
// any leftover decimal point. This should only matter if the sum of
// your original array isn't an integer.
lowerSum = Math.floor(
parseFloat(
lowerSum.toFixed(2)
)
);
// sort tempArr based on difference
tempArr.sort(function (a, b) {
if ( a.difference < b.difference ) {
return -1;
}
if ( a.difference > b.difference ) {
return 1;
}
return 0;
}).reverse();
// Add 1 to those most likely to round up to the
// next number so that the difference is nullified.
i = 0;
while (lowerSum) {
tempArr[i].result += 1;
lowerSum--;
i++;
}
// sort tempArr back to original index
tempArr.sort(function (a, b) {
return a.index - b.index;
});
// return array of results
return tempArr.map(function (item) { return item.result; });
}
describe("roundFloats", function() {
it('should round lists of floats to int', function () {
var test = [
{
unprocessed: [0.02, 0.03, 0.05, 0.06, 0.07, 0.08,
0.09, 0.1, 0.11, 0.12, 0.13, 0.14], // = 1
processed: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] // = 1
},
{
unprocessed: [0.1, 0.3, 0.4, 0.4, 0.8], // = 2
processed: [0, 0, 0, 1, 1] // = 2
},
{
unprocessed: [0.1, 0.1, 0.1, 0.1, 0.1,
0.1, 0.1, 0.1, 0.1, 0.1], // = 0.9999999999999999
processed: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1] // = 1
},
{
unprocessed: [0.4, 0.4, 0.4, 0.4, 9.2, 9.2], // = 20
processed: [0, 0, 1, 1, 9, 9] // = 20
},
{
unprocessed: [0.5, 0.5, 11], // = 12
processed: [0, 1, 11] // = 12
},
{
unprocessed: [45.5, 54.5], // = 100
processed: [45, 55] // = 100
},
{
unprocessed: [0.8, 1.3], // = 2.1
processed: [1, 1] // = 2
},
{
unprocessed: [2.8, 4.7, 7.8, 10.9, 5.6], // = 31.800000000000004
processed: [3, 4, 8, 11, 5] // = 31
},
{
unprocessed: [0.99, 0.51, 0.34, 0.44, 0.8, 0.91], // = 3.99
processed: [1, 0, 0, 0, 1, 1] // = 3
},
{
unprocessed: [33.333333, 33.333333, 33.333333], // = 99.999999
processed: [33, 33, 34] // = 100
},
{
unprocessed: [100],
processed: [100]
},
{
unprocessed: [],
processed: []
}
];
for (var i = 0; i < test.length; i++) {
expect(
roundFloats(test[i].unprocessed)
).toEqual(
test[i].processed
);
};
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment