546

I have the following JavaScript syntax:

var discount = Math.round(100 - (price / listprice) * 100);

This rounds up to the whole number. How can I return the result with two decimal places?

Smudger
  • 10,451
  • 29
  • 104
  • 179
  • Because of sometimes .xxx5 into .xxx4999..., so I change 5 to 6. Is this wrong? Yes, if the decimal is lower than 2 (because want 2 decimal). `function fround(n,r){var t=String(n).split(".");return t[1].length>r&&5==t[1][t[1].length-1]&&(t[1]=t[1].slice(0,-1)+"6"),Math.round(Number(t.join("."))*10**r)/10**r}` – Transamunos Jul 12 '20 at 00:21
  • Found another way: `function fround(n,r=2){return Math.round(Math.round(n*10**(r+1))/10)/10**r}` – Transamunos Jul 12 '20 at 01:58
  • simpler option: discount = parseFloat(discount.toFixed(2)) – alex Dec 14 '22 at 21:51

12 Answers12

981

NOTE - See Edit 4 if 3 digit precision is important

var discount = (price / listprice).toFixed(2);

toFixed will round up or down for you depending on the values beyond 2 decimals.

Example: http://jsfiddle.net/calder12/tv9HY/

Documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed

Edit - As mentioned by others this converts the result to a string. To avoid this:

var discount = +((price / listprice).toFixed(2));

Edit 2- As also mentioned in the comments this function fails in some precision, in the case of 1.005 for example it will return 1.00 instead of 1.01. If accuracy to this degree is important I've found this answer: https://stackoverflow.com/a/32605063/1726511 Which seems to work well with all the tests I've tried.

There is one minor modification required though, the function in the answer linked above returns whole numbers when it rounds to one, so for example 99.004 will return 99 instead of 99.00 which isn't ideal for displaying prices.

Edit 3 - Seems having the toFixed on the actual return was STILL screwing up some numbers, this final edit appears to work. Geez so many reworks!

var discount = roundTo((price / listprice), 2);

function roundTo(n, digits) {
  if (digits === undefined) {
    digits = 0;
  }

  var multiplicator = Math.pow(10, digits);
  n = parseFloat((n * multiplicator).toFixed(11));
  var test =(Math.round(n) / multiplicator);
  return +(test.toFixed(digits));
}

See Fiddle example here: https://jsfiddle.net/calder12/3Lbhfy5s/

Edit 4 - You guys are killing me. Edit 3 fails on negative numbers, without digging into why it's just easier to deal with turning a negative number positive before doing the rounding, then turning it back before returning the result.

function roundTo(n, digits) {
    var negative = false;
    if (digits === undefined) {
        digits = 0;
    }
    if (n < 0) {
        negative = true;
        n = n * -1;
    }
    var multiplicator = Math.pow(10, digits);
    n = parseFloat((n * multiplicator).toFixed(11));
    n = (Math.round(n) / multiplicator).toFixed(digits);
    if (negative) {
        n = (n * -1).toFixed(digits);
    }
    return n;
}

Fiddle: https://jsfiddle.net/3Lbhfy5s/79/

Matt Simerson
  • 1,001
  • 1
  • 10
  • 22
Rick Calder
  • 18,310
  • 3
  • 24
  • 41
  • Thanks Rick. this adds the two decimal places as in 00, but doesn't provide the math result. s0 the desired result of 3.18 is now being displayed as 3.00? – Smudger Apr 02 '13 at 11:29
  • ahh I see, .toFixed should do the rounding for you, see edited answer. – Rick Calder Apr 02 '13 at 11:37
  • 46
    .toFixed returns a string not a number however - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed – Daniel Flippance May 05 '14 at 18:11
  • 4
    make sure the item you use toFixed on is a number, you can parseFloat it first. – DrCord May 22 '14 at 01:25
  • One need to parsefloat or make sure that you are dealing with numbers that have decimals before applying toFixed(). "With Great Power Comes Great Responsibility" Lets program wisely. – Clain Dsilva Jun 07 '14 at 13:13
  • 1
    Downvote, because this converts the number to a string. – thomasfuchs Feb 21 '15 at 13:18
  • 98
    Seems like an awful lot of work to turn a number to a string, back to a number. I've found that `Math.round(x * 100) / 100;` is the easiest, simplest way to round to two decimal places. – M - Jun 05 '15 at 23:33
  • 10
    Downvote, because it's very inefficient (on my machine it takes 400x longer than Math.round) http://jsperf.com/round-numbers-to-2-digits – Simon Aug 06 '15 at 16:29
  • 1
    As mentioned below, it rounds 5 (eg 1.005 -> 1.00) down fyi – atomkirk Feb 24 '17 at 22:13
  • @atomkirk Yup, that's true, it's a bit of a quirk of languages that use double precision floating point numbers (explained in one of the comments here: http://stackoverflow.com/questions/20701029/rounding-issue-in-math-round-tofixed) That being said the OP has prices in his post, which are very rarely going to be expressed in 3 decimal places if ever. It's still a fine solution for most things short of anything scientific I would assume. – Rick Calder Feb 28 '17 at 20:07
  • @MarcoDelValle Your suggested solution does not always return correct results. For example: Math.round(1.005 * 100) / 100 == 1, when it should be 1.01. Same goes for this answer: +(1.005.toFixed(2)) == 1, which is not correct. – Laurynas Lazauskas Mar 02 '17 at 08:47
  • @LaurynasLazauskas Which is the exact same comment made two above, on a 4 year old answer. I'll give you the same response, in the OP he mentioned prices, which are almost never going to be represented in 3 decimal place numbers. Context matters. – Rick Calder Mar 02 '17 at 15:17
  • 3
    @RickCalder Missed that comment, sorry. But I am not sorry for commenting on a 4 year old answer that is displayed pretty high in the results when Googled "js rounding float to decimal places" when the answer isn't completely correct or at least doesn't specify its shortcomings. And your point "prices, which are *almost* never going to be represented in 3 decimal place" is very weak, as price representation before rounding can easily have many decimal digits when involved in some calculation (eg., after discounts). – Laurynas Lazauskas Mar 02 '17 at 15:54
  • @LaurynasLazauskas - I've updated the answer taking your concerns into consideration. I'm not positive this is terribly performant, but it does appear to work in the cases you mentioned. – Rick Calder Mar 10 '17 at 16:03
  • something odd can't figure that how its happening !! 1223.475 -->1223.47 but 3535.475 -->3535.48. notice the decimal fraction is same in both cases but the rounding is acting different. ( midpointrounding ) – Jay Mar 21 '17 at 05:11
  • @Jay - At this point I'm not even going to pretend to understand JS and numbers any more, but the final edit appears to solve the problem. It was me adding toFixed to the return value immediately. If you push the result of roundTo to a variable it rounds correctly, then push that into toFixed appears to always return the expected result. So much work to do something that seems so simple lol. – Rick Calder Mar 21 '17 at 12:01
  • 2
    `return +(test.toFixed(2));` should probably use the `digits` argument. – duckbrain Jun 08 '17 at 19:46
  • Minus does not work: roundTo(-1.005, 2) gives -1 (and not -1.01) – DavidDunham Sep 25 '17 at 06:00
  • 3
    @DavidDunham ... you guys are awfully picky for a language that isn't great for math to begin with lol. See Edit 4 – Rick Calder Oct 20 '17 at 18:00
  • Much like a bank, I need to round up if there is any value beyond the 3rd decimal point for ex: 9.422222222 needs to round up to 9.43 ... how would I modify this to do that? – Prescott Chartier Mar 15 '18 at 19:49
  • 1
    Scratch that, change Math.round to Math.ceil .... – Prescott Chartier Mar 15 '18 at 19:56
  • 10
    Seriously enough, the point of this was to answer the initial question, which this answer does. If you have a better answer by all means add it, but nitpicking a 6 year old answer to how to round to 2 decimal places because it doesn't round to 20 decimal places is exactly what is wrong with this site. – Rick Calder Jan 04 '19 at 13:39
  • @RickCalder thanks for this solution but I think you should have `toFixed(digits);` where you have `toFixed(2);` – Matúš Matula Jul 20 '20 at 18:42
  • @MatúšMatula ha, nice catch. Fixed. – Rick Calder Jul 27 '20 at 15:52
  • pass in a boolean to not show decimals if whole number? If digits is 5 and I have a result that is 1.00000 I would just like it to say 1 – Ashkan Hovold Sep 03 '20 at 09:37
  • 1
    Does not work correctly with 8200.005 example: https://jsfiddle.net/rdLse87k/ – P Roitto Nov 06 '20 at 09:39
  • i personally prefer `parseFloat()` instead of `Number()` because of its speed : `parseFloat(val.toFixed(2));` – user889030 Mar 14 '22 at 10:39
  • 2
    All the messy answers here make me feel that we should just use a library for numerical calculations! – ankush981 Mar 19 '22 at 13:02
  • 1
    @ankush981 I agree, it's sad that a language is this bad that it doesn't provide an easy way. We may as well just all learn binary by now. – Dan Chase Jul 09 '23 at 19:40
167

If you use a unary plus to convert a string to a number as documented on MDN.

For example:+discount.toFixed(2)

Alexis Tyler
  • 1,394
  • 6
  • 30
  • 48
John Gietzen
  • 48,783
  • 32
  • 145
  • 190
63

The functions Math.round() and .toFixed() is meant to round to the nearest integer. You'll get incorrect results when dealing with decimals and using the "multiply and divide" method for Math.round() or parameter for .toFixed(). For example, if you try to round 1.005 using Math.round(1.005 * 100) / 100 then you'll get the result of 1, and 1.00 using .toFixed(2) instead of getting the correct answer of 1.01.

You can use following to solve this issue:

Number(Math.round(100 - (price / listprice) * 100 + 'e2') + 'e-2');

Add .toFixed(2) to get the two decimal places you wanted.

Number(Math.round(100 - (price / listprice) * 100 + 'e2') + 'e-2').toFixed(2);

You could make a function that will handle the rounding for you:

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

Example: https://jsfiddle.net/k5tpq3pd/36/

Alternative

You can add a round function to Number using prototype. I would not suggest adding .toFixed() here as it would return a string instead of number.

Number.prototype.round = function(decimals) {
    return Number((Math.round(this + "e" + decimals)  + "e-" + decimals));
}

and use it like this:

var numberToRound = 100 - (price / listprice) * 100;
numberToRound.round(2);
numberToRound.round(2).toFixed(2); //Converts it to string with two decimals

Example https://jsfiddle.net/k5tpq3pd/35/

Source: http://www.jacklmoore.com/notes/rounding-in-javascript/

zhulien
  • 5,145
  • 3
  • 22
  • 36
Arne H. Bitubekk
  • 2,963
  • 1
  • 27
  • 34
  • 7
    They might've voted down because it's not really an error. `Math.round()` is meant to round to the nearest whole integer, and people generally frown upon and try not to change built-in methods like this in case someone is expecting that functionality to be like it was when they go to code behind you. But your code is sound, and +1 for pointing out the limitations of `Math.round()`. – vapcguy Apr 01 '15 at 03:50
  • +1 for feedback! I've refraised the answer based on your feedback so hope more will find it more helpful and correct – Arne H. Bitubekk Apr 02 '15 at 07:37
  • How do you force 2 decimals? In your example, a number with 1 decimal doesn't fill in the second decimal slot with a "0", e.g. "1.3" rounds to "1.3" instead of "1.30". – zeeshan Sep 01 '16 at 04:00
  • To achive this you have to convert the number to string, the simplest way is to use .toFixed(2) (where 2 is the number of decimals you want). It's covered in the answer I gave :) – Arne H. Bitubekk Sep 01 '16 at 07:47
  • 6
    This approach doesn't work for numbers whose string representation already has an exponent. Anything `1e+21` or higher, for example, returns `NaN`. – qntm Oct 24 '16 at 19:58
  • 1
    This does not work with some negative values. e.g. `Number(Math.round(-1.225 + 'e2') + 'e-2')` returns **-1.22** when `Number(Math.round(1.225 + 'e2') + 'e-2')` returns **1.23** – P Roitto Jul 29 '21 at 12:15
  • Correct if I'm wrong, but `exp` numbers can be bypassed through a guard clause. JS looks to not support decimals with that large numbers, just test `(1e+20 + 1.1) - 1e+20` OR `1.0000000000000001`; and too small `exp` numbers don't matter when rounding to ONLY 2 decimal places. Anyway, for numbers < 0.0000001 (that eval to "exp" on toString()) we can use some tricks to prevent that like `Math.round((Number((Math.round(1 + 0.0000009 + "e6") + "e-6")) * 1e6 - 1e6))/1e6`?? – Andre Figueiredo Oct 27 '22 at 14:27
  • 1
    **CAUTION**: This will return `NaN` if the value uses `e` notation already, which can happen unexpectedly due to floating point errors. For example `49.8 - 25.2 - 24.6` gives `-3.552713678800501e-15`. – SystemParadox Dec 15 '22 at 17:55
38

To get the result with two decimals, you can do like this :

var discount = Math.round((100 - (price / listprice) * 100) * 100) / 100;

The value to be rounded is multiplied by 100 to keep the first two digits, then we divide by 100 to get the actual result.

Cattode
  • 856
  • 5
  • 11
  • 2
    @DejayClayton Actually that's the correct way to do this, without going through the kludge of converting to a string and back to a number. – thomasfuchs Feb 21 '15 at 13:18
  • 2
    Sorry, I don't know what I was actually complaining about, other than the fact that it looks like a jumble of undifferentiated magic numbers. But it's concise, I guess. – Dejay Clayton Apr 30 '15 at 22:37
  • If the number results in 1.005 for instance, this will round to 1 instead of 1.1. Discount below would equal 1 instead of 1.1 let price = 98.995 let listprice = 100 var discount = Math.round((100 - (price / listprice) * 100) * 100) / 100; – Bryce Sep 08 '17 at 13:26
35

The best and simple solution I found is

function round(value, decimals) {
 return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}   
round(1.005, 2); // 1.01
Naga Srinu Kapusetti
  • 1,563
  • 15
  • 12
  • 2
    The first solution that works! –  Nov 01 '18 at 03:44
  • 3
    This solution does not work for values that JS already represents in scientific notation in string form. Try calling round(0.0000000001, 2) and you get the wrong answer. It's also completely silly to switch between numbers and strings *twice* for such a simple mathematical operation. – JounceCracklePop Mar 06 '19 at 19:56
  • 3
    This solution does not work for typescript too – Muhammed Moussa Aug 12 '20 at 12:26
  • 1
    Am I missing something or isn't this just a copy of Arne Bitubeck's answer above? – Charles Wood Jun 07 '22 at 14:58
22

try using discount.toFixed(2);

Gonzalo.-
  • 12,512
  • 5
  • 50
  • 82
Harish
  • 1,469
  • 16
  • 43
  • 1
    This is nice, but returns a string rather than a decimal. So if you do this and then `+3` you end up with something like "11.113" (where the `discount` value was 11.109897 for example. So, if you do this, and need it to be a number for further calculations, either parseInt it, or multiply it by 1. – Luke Stevenson Jul 02 '20 at 03:51
  • 1
    You'd sometimes need to use parseFloat(), not parseInt(). **parseInt()** removes the digits after the decimal to round it to the nearest whole integer. **parseFloat()** keeps the digits, so that you may use this for currency or more precise calculations. – Akel Jul 28 '20 at 14:31
21

I think the best way I've seen it done is multiplying by 10 to the power of the number of digits, then doing a Math.round, then finally dividing by 10 to the power of digits. Here is a simple function I use in typescript:

function roundToXDigits(value: number, digits: number) {
    value = value * Math.pow(10, digits);
    value = Math.round(value);
    value = value / Math.pow(10, digits);
    return value;
}

Or plain javascript:

function roundToXDigits(value, digits) {
    if(!digits){
        digits = 2;
    }
    value = value * Math.pow(10, digits);
    value = Math.round(value);
    value = value / Math.pow(10, digits);
    return value;
}
Bryce
  • 664
  • 6
  • 17
  • 1
    upvote for the **description** of the functions before writing the code :) – vibs2006 Sep 07 '17 at 07:34
  • This fails the `1.005` test. `roundToXDigits(1.005, 2) => 1` – brainbag Jan 22 '18 at 14:17
  • 2
    @brainbag yeah but only due to floating point math. `1.005 * Math.pow(10, 2)` is `100.49999999999999` so rounds down. There's not much you can do about that without pulling in a BigNumbers lib or something. `roundToXDigits(1.05, 1)` returns correctly, as does `roundToXDigits(1.0005, 3)`, etc. – Molomby Aug 20 '19 at 04:13
11

A small variation on the accepted answer. toFixed(2) returns a string, and you will always get two decimal places. These might be zeros. If you would like to suppress final zero(s), simply do this:

var discount = + ((price / listprice).toFixed(2));

Edited: I've just discovered what seems to be a bug in Firefox 35.0.1, which means that the above may give NaN with some values.
I've changed my code to

var discount = Math.round(price / listprice * 100) / 100;

This gives a number with up to two decimal places. If you wanted three, you would multiply and divide by 1000, and so on.
The OP wants two decimal places always, but if toFixed() is broken in Firefox it needs fixing first.
See https://bugzilla.mozilla.org/show_bug.cgi?id=1134388

HalfTitle
  • 137
  • 1
  • 4
  • You can also parseFloat the result: var discount = parseFloat((price / listprice).toFixed(2)); – Emilie Feb 13 '15 at 19:41
  • the second solution can result in rounding 1.005 to 1 instead of 1.1 (as mentioned by Laurynas Lazauskas below). I've updated my answer which does convert from numbers to strings but should round in this edge case to 1.1 – Bryce Sep 08 '17 at 13:27
  • 1
    Shouldn't it round to 1.01? – Deejers Feb 27 '18 at 15:23
11

Fastest Way - faster than toFixed():

TWO DECIMALS

x      = .123456
result = Math.round(x * 100) / 100  // result .12

THREE DECIMALS

x      = .123456
result = Math.round(x * 1000) / 1000      // result .123
Kamy D
  • 1,071
  • 2
  • 13
  • 23
  • 7
    Fast but wrong: `Math.round(1.005 * 100) / 100` returns `1` – Christophe Roussy Oct 12 '17 at 08:05
  • I had to use Math.round(num * 100)/100 approach because of below scenario 3.555 --> 3.56, // while using toFixed(2) although i wanted 3.555 --> 3.55 // worked when i used Math.round approach – Nikhil Kamani May 07 '20 at 05:06
  • This is the actual correct answer. I'd just add that for the specific discount scenario, the simplified code would be: `var discount = Math.round(10000 * (1 - price / listprice)) / 100;` @ChristopheRoussy, the example you provided is actually correct and this result is caused by the fact that base-2 floating point can't represent all base-10 decimals precisely. In this case 1.005 * 100 in JS is actually slightly less than than 100.5, so it's rounded towards 100 not 101. – Pedro Pedruzzi Sep 11 '21 at 16:00
3
function round(num,dec)
{
    num = Math.round(num+'e'+dec)
    return Number(num+'e-'+dec)
}
//Round to a decimal of your choosing:
round(1.3453,2)
bdkopen
  • 494
  • 1
  • 6
  • 16
  • I like this solution. Short and simple but one issue which can be solved by just adding 'toFixed(dec)' in the end. `Number(Math.round(num + 'e2') + 'e-2').toFixed(2)` – Harry Nov 20 '17 at 12:44
  • This is exact copy of another answer: https://stackoverflow.com/a/42368487/5458806 – Ahmad Mobaraki Sep 14 '21 at 02:00
1

Here is a working example

var value=200.2365455;
result=Math.round(value*100)/100    //result will be 200.24
0

To handle rounding to any number of decimal places, a function with 2 lines of code will suffice for most needs. Here's some sample code to play with.



    var testNum = 134.9567654;
    var decPl = 2;
    var testRes = roundDec(testNum,decPl);  
    alert (testNum + ' rounded to ' + decPl + ' decimal places is ' + testRes);

    function roundDec(nbr,dec_places){
        var mult = Math.pow(10,dec_places);
        return Math.round(nbr * mult) / mult;
    }

user3232196
  • 169
  • 1
  • 5