Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rounding away from zero in Javascript

We are building a table in Javascript with Handsontable representing currency amounts. We give the user the possibility of render the amounts with two decimal places or no decimal places (it's a requirement from the client). And then we find things like this:

Column A     Column B      Column C = A + B
-------------------------------------------
-273.50       273.50       0                 Two decimals
-273          274          0                 No decimals

Investigating a little we came to find that the basic rounding function in Javascript, Math.round(), works like this:

If the fractional portion is exactly 0.5, the argument is rounded to the next integer in the direction of +∞. Note that this differs from many languages' round() functions, which often round this case to the next integer away from zero, instead (giving a different result in the case of negative numbers with a fractional part of exactly 0.5).

As we are dealing with currency amounts, we do not care about what happens after the second decimal place, so we chose to add -0.0000001 to any negative value in the table. Thus, when rendering the values with two or no decimals, now we get the proper results, as Math.round(-273.5000001) = -274, and Math.round(-273.4900001) is still -273.

Nonetheless, we would like to find a finer solution to this problem. So what is the best, most elegant way to achieve this (that does not require modifying the original numeric value)? Note that we do not directly call Math.round(x), we just tell Handsontable to format a value with a given number of decimal places.

like image 966
Charlie Avatar asked May 31 '26 11:05

Charlie


2 Answers

Just some variations on how you could implement the desired behaviour, with or without using Math.round(). And the proof that these functions work.

It's up to you which version speaks to you.

const round1 = v => v<0 ? Math.ceil(v - .5) : Math.floor(+v + .5);

const round2 = v => Math.trunc(+v + .5 * Math.sign(v));

const round3 = v => Math.sign(v) * Math.round(Math.abs(v));

const round4 = v => v<0 ? -Math.round(-v): Math.round(v);

const funcs = [Number, Math.round, round1, round2, round3, round4];

[
  -273.50, -273.49, -273.51, 
   273.50,  273.49,  273.51
].forEach(value => console.log(
  Object.fromEntries(funcs.map(fn => [fn.name, fn(value)]))
));
like image 119
Thomas Avatar answered Jun 03 '26 01:06

Thomas


Math.round() works as wanted for zero and positive numbers. For negative numbers, convert to a positive and then multiply back:

/**
 * @arg {number} num
 * @return {number}
 */
function round(num) {
  return num < 0 ? -Math.round(-num) : Math.round(num);
}

This seems to work correctly for these inputs:

round(-3) = -3
round(-2.9) = -3
round(-2.8) = -3
round(-2.7) = -3
round(-2.6) = -3
round(-2.5) = -3
round(-2.4) = -2
round(-2.3) = -2
round(-2.2) = -2
round(-2.1) = -2
round(-2) = -2
round(-1.9) = -2
round(-1.8) = -2
round(-1.7) = -2
round(-1.6) = -2
round(-1.5) = -2
round(-1.4) = -1
round(-1.3) = -1
round(-1.2) = -1
round(-1.1) = -1
round(-1) = -1
round(-0.9) = -1
round(-0.8) = -1
round(-0.7) = -1
round(-0.6) = -1
round(-0.5) = -1
round(-0.4) = 0
round(-0.3) = 0
round(-0.2) = 0
round(-0.1) = 0
round(0) = 0
round(0.1) = 0
round(0.2) = 0
round(0.3) = 0
round(0.4) = 0
round(0.5) = 1
round(0.6) = 1
round(0.7) = 1
round(0.8) = 1
round(0.9) = 1
round(1) = 1
round(1.1) = 1
round(1.2) = 1
round(1.3) = 1
round(1.4) = 1
round(1.5) = 2
round(1.6) = 2
round(1.7) = 2
round(1.8) = 2
round(1.9) = 2
round(2) = 2
round(2.1) = 2
round(2.2) = 2
round(2.3) = 2
round(2.4) = 2
round(2.5) = 3
round(2.6) = 3
round(2.7) = 3
round(2.8) = 3
round(2.9) = 3
round(3) = 3

I think negatives will always round to another negative or zero so this multiplication order is correct.


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!