最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

math - Rounding away from zero in Javascript - Stack Overflow

programmeradmin2浏览0评论

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.

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.

Share Improve this question edited Apr 7, 2017 at 8:38 Charlie asked Apr 7, 2017 at 8:16 CharlieCharlie 6641 gold badge10 silver badges23 bronze badges 5
  • var rounded = (val < 0) ? Math.round(val - 0.5) : Math.round(val+0.5); – Taha Paksu Commented Apr 7, 2017 at 8:19
  • 4 Actually when dealing with currency you should just use integer values in the smallest denomination. Add the decimal point just for display. This will prevent a lot of problems with calculations in that area. – Sirko Commented Apr 7, 2017 at 8:20
  • @Sirko that's a interesting point, but if we have, say, -27350 cents of euro (saved in the smallest denomination), when I am to display that with no decimal places in the euro denomination, I would still have -273 as the value displayed, as I would have to divide the value by 100 before. – Charlie Commented Apr 7, 2017 at 8:26
  • [33, 2.3, 53.34].map(x=>x.toFixed(2)).join(", ") == "33.00, 2.30, 53.34" thus all you need is .toFixed(2) – dandavis Commented Apr 7, 2017 at 8:28
  • 1 @CarlosAlejo I didn't suggest this will solve the concret problem at hand. This was just a mere hint, which might save you some trouble when dealing with currencies, where floating point problems are usually more important than in other putations. – Sirko Commented Apr 7, 2017 at 8:34
Add a ment  | 

3 Answers 3

Reset to default 2

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)]))
));

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.

As @dandavis noted in the ments, the best way to handle this is to use .toFixed(2) instead of Math.round().

Math.round(), as you noted, rounds half up.

.toFixed() will round half away from zero. We designate the 2 in there to indicate the amount of decimals to use when doing the rounding.

发布评论

评论列表(0)

  1. 暂无评论