0

Can someone please kindly explain to me why "Math.round" is not rounding to the nearest integer correctly? I have a 2D array, in which I want to find out the average of each columns.

const nums = [[ 20,10,35 ],[70,179,30],[207,223,8],[38,53,52],[234,209,251],[35,94,2]]

After transposing the each columns into rows:

[
  [ 20, 70, 207, 38, 234, 35 ],
  [ 10, 179, 223, 53, 209, 94 ],
  [ 35, 30, 8, 52, 251, 2 ]
]

I was able to return the average to be rounded off.

const avg = (arr) => arr[0].map((_,i) => arr.reduce((sum,v) => sum + Math.round(v[i]/arr.length),0))

Unfortunately, "Math.round" is not rounding the result correctly:

before  // [ 100.66666666666667, 127.99999999999999, 63.00000000000001 ]
after   // [ 101, 129, 63 ]

As you can see the 2nd element shoud have rouned off to "128" instead of "129". I've tried testing as followed without problem:

console.log(Math.round(127.99999999999999)); // 128

However, "Math.round" from my function is outputting incorrect result. Is this a glitch? Or is there something wrong with my function? Any feedback will be greatly appreciated, million thanks in advance :)

UPDATE:

Thanks to zer00ne's amazing tip, I was able to refactored the code to make the function work as followed:

const avg = (arr) => arr[0].map((_,i) => Math.round(arr.reduce((sum,v) => sum + v[i]/arr.length,0)));

console.log(avg(nums)); // [ 101, 128, 63 ]
noirsociety
  • 314
  • 1
  • 2
  • 9
  • 3
    Shouldn't you add everything first then divide `sum` by `arr.length` for an average? – zer00ne Jul 07 '22 at 19:37
  • 1
    How did you get the `before // [ 100.66666666666667, 127.99999999999999, 63.00000000000001 ]`? Your code calculates `113 + Math.round(15.666666666666666)` and the result of that is most definitely 129. – VLAZ Jul 07 '22 at 19:47
  • 1
    I guess by just removing the Math.round. But I agree with zer00ne. This is not how to calculate an average. In your case the average of [20, 20, 20] would be Math.round(20/3) + Math.round(20/3) + Math.round(20/3) = 7 + 7 + 7 = 21. – seb Jul 07 '22 at 19:51
  • 2
    @zer00ne Dividing each element by `arr.length` before summing is fine. What's not fine is rounding each element individually, before summing. – Bergi Jul 07 '22 at 19:56

1 Answers1

3

JavaScript math is at times inaccurate because it stores numbers in memory as binary floats, for details see Why JavaScript is Bad At Math. Anyways, if you're looking to have an array of three averages see the example below.

const data = [
  [ 20, 70, 207, 38, 234, 35 ],
  [ 10, 179, 223, 53, 209, 94 ],
  [ 35, 30, 8, 52, 251, 2 ]
];

let output = data.map(sub => Math.round(
  sub.reduce(
    (sum, cur) => sum + cur)/sub.length)
);

console.log(output);
zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • 1
    Thanks for the tips :) I was able to refactor the code and made it work: `const avg = (arr) => arr[0].map((_,i) => Math.round(arr.reduce((sum,v) => sum + v[i]/arr.length,0)));` Thank you so much for pointing me in the right direction. – noirsociety Jul 07 '22 at 19:57