javascript - Rounding up or down when 0.5 - Stack Overflow

I am having an issue with the way Javascript is rounding numbers when hitting 0.5.I am writing levies

I am having an issue with the way Javascript is rounding numbers when hitting 0.5. I am writing levies calculators, and am noticing a 0.1c discrepancy in the results.

The problem is that the result for them is 21480.705 which my application translates into 21480.71, whereas the tariff says 21480.70.

This is what I am seeing with Javascript:

(21480.105).toFixed(2)
"21480.10"
(21480.205).toFixed(2)
"21480.21"
(21480.305).toFixed(2)
"21480.31"
(21480.405).toFixed(2)
"21480.40"
(21480.505).toFixed(2)
"21480.51"
(21480.605).toFixed(2)
"21480.60"
(21480.705).toFixed(2)
"21480.71"
(21480.805).toFixed(2)
"21480.81"
(21480.905).toFixed(2)
"21480.90"

Questions:

  • What the hell is going on with this erratic rouding?
  • What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?

I am having an issue with the way Javascript is rounding numbers when hitting 0.5. I am writing levies calculators, and am noticing a 0.1c discrepancy in the results.

The problem is that the result for them is 21480.705 which my application translates into 21480.71, whereas the tariff says 21480.70.

This is what I am seeing with Javascript:

(21480.105).toFixed(2)
"21480.10"
(21480.205).toFixed(2)
"21480.21"
(21480.305).toFixed(2)
"21480.31"
(21480.405).toFixed(2)
"21480.40"
(21480.505).toFixed(2)
"21480.51"
(21480.605).toFixed(2)
"21480.60"
(21480.705).toFixed(2)
"21480.71"
(21480.805).toFixed(2)
"21480.81"
(21480.905).toFixed(2)
"21480.90"

Questions:

  • What the hell is going on with this erratic rouding?
  • What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?
Share Improve this question asked Mar 11, 2019 at 6:05 MercMerc 17.1k18 gold badges86 silver badges132 bronze badges 8
  • 2 Answer to question 1 – Robby Cornelissen Commented Mar 11, 2019 at 6:08
  • 1 @robby console.log(21480.105) gets logged correctly though. – Jonas Wilms Commented Mar 11, 2019 at 6:26
  • @kaiido my point is that the number itself is accurate, this is a flaw in the rounding algorithm toFixed is using (or am I wrong pletely)? – Jonas Wilms Commented Mar 11, 2019 at 6:35
  • @JonasWilms wrong pletely. You're letting yourself be deceived by JavaScript's policy on how to convert floating-point values to string. It will try to display something shorter than the actual number stored in the variable. The actual number is not exactly 21480.105, yet that is what is displayed. That should be your clue that your reasoning was missing something. – kumesana Commented Mar 11, 2019 at 6:37
  • Never ever use floating point for calculations where you need an exact result, e.g., when working with money. – Joe Commented Mar 11, 2019 at 7:17
 |  Show 3 more ments

5 Answers 5

Reset to default 3

So as some of the others already explained the reason for the 'erratic' rounding is a floating point precision problem. You can investigate this by using the toExponential() method of a JavaScript number.

(21480.905).toExponential(20)
#>"2.14809049999999988358e+4"
(21480.805).toExponential(20)
#>"2.14808050000000002910e+4"

As you can see here 21480.905, gets a double representation that is slightly smaller than 21480.905, while 21480.805 gets a double representation slightly larger than the original value. Since the toFixed() method works with the double representation and has no idea of your original intended value, it does all it can and should do with the information it has.

One way to work around this, is to shift the decimal point to the number of decimals you require by multiplication, then use the standard Math.round(), then shift the decimal point back again, either by division or multiplication by the inverse. Then finally we call toFixed() method to make sure the output value gets correctly zero-padded.

var x1 = 21480.905;
var x2 = -21480.705;

function round_up(x,nd)
{
  var rup=Math.pow(10,nd);
  var rdwn=Math.pow(10,-nd); // Or you can just use 1/rup
  return (Math.round(x*rup)*rdwn).toFixed(nd)
}
function round_down(x,nd)
{
  var rup=Math.pow(10,nd);
  var rdwn=Math.pow(10,-nd); 
  return (Math.round(x*-rup)*-rdwn).toFixed(nd)
}

function round_tozero(x,nd)
{
   return x>0?round_down(x,nd):round_up(x,nd) 
}



console.log(x1,'up',round_up(x1,2));
console.log(x1,'down',round_down(x1,2));
console.log(x1,'to0',round_tozero(x1,2));

console.log(x2,'up',round_up(x2,2));
console.log(x2,'down',round_down(x2,2));
console.log(x2,'to0',round_tozero(x2,2));

Finally: Encountering a problem like this is usually a good time to sit down and have a long think about wether you are actually using the correct data type for your problem. Since floating point errors can accumulate with iterative calculation, and since people are sometimes strangely sensitive with regards to money magically disappearing/appearing in the CPU, maybe you would be better off keeping monetary counters in integer 'cents' (or some other well thought out structure) rather than floating point 'dollar'.

The why -

You may have heard that in some languages, such as JavaScript, numbers with a fractional part are calling floating-point numbers, and floating-point numbers are about dealing with approximations of numeric operations. Not exact calculations, approximations. Because how exactly would you expect to pute and store 1/3 or square root of 2, with exact calculations?

If you had not, then now you've heard of it.

That means that when you type in the number literal 21480.105, the actual value that ends up stored in puter memory is not actually 21480.105, but an approximation of it. The value closest to 21480.105 that can be represented as a floating-point number.

And since this value is not exactly 21480.105, that means it is either slightly more than that, or slightly less than that. More will be rounded up, and less will be rounded down, as expected.

The solution -

Your problem es from approximations, that it seems you cannot afford. The solution is to work with exact numbers, not approximate.

Use whole numbers. Those are exact. Add in a fractional dot when you convert your numbers to string.

This works in most cases. (See note below.)

The rounding problem can be avoided by using numbers represented in exponential notation:

function round(value, decimals) {
  return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}

console.log(round(21480.105, 2).toFixed(2));

Found at http://www.jacklmoore./notes/rounding-in-javascript/

NOTE: As pointed out by Mark Dickinson, this is not a general solution because it returns NaN in certain cases, such as round(0.0000001, 2) and with large inputs. Edits to make this more robust are wele.

You could round to an Integer, then shift in a ma while displaying:

function round(n, digits = 2) {
  // rounding to an integer is accurate in more cases, shift left by "digits" to get the number of digits behind the ma
  const str = "" + Math.round(n * 10 ** digits);

  return str
    .padStart(digits + 1, "0") // ensure there are enough digits, 0 -> 000 -> 0.00
    .slice(0, -digits) + "." + str.slice(-digits); // add a ma at "digits" counted from the end
}

What the hell is going on with this erratic rouding?

Please reference the cautionary Mozilla Doc, which identifies the cause for these discrepancies. "Floating point numbers cannot represent all decimals precisely in binary which can lead to unexpected results..."

Also, please reference Is floating point math broken? (Thank you Robby Cornelissen for the reference)

What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?

Use a JS library like accounting.js to round, format, and present currency.

For example...

function roundToNearestCent(rawValue) {
  return accounting.toFixed(rawValue, 2);
}

const roundedValue = roundToNearestCent(21480.105);
console.log(roundedValue);
<script src="https://binatronics./openexchangerates/accounting.js/master/accounting.js"></script>

Also, consider checking out BigDecimal in JavaScript.

Hope that helps!

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744395435a4572117.html

相关推荐

  • javascript - Rounding up or down when 0.5 - Stack Overflow

    I am having an issue with the way Javascript is rounding numbers when hitting 0.5.I am writing levies

    7天前
    20

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信