87

I'm using javascript to bind to some checkboxes, and the toFixed(2) is not rounding up. Any ideas why it's not rounding? For instance, if the number is 859.385 it's only displaying 859.38 instead of 859.39.

I've also read that the toFixed can round differently depending on which browser you are using, anyone know of a way around this so that my javascript calculations match my php calculations?

var standardprice = parseFloat($('#hsprice_'+this.id.split('_')[1]).val());
var price =  parseFloat($('#hprice_'+this.id.split('_')[1]).val());
var discount =  parseFloat($('#hdiscount_'+this.id.split('_')[1]).val());
var deposit =  parseFloat($('#hdeposit_'+this.id.split('_')[1]).val());

var currSprice = parseFloat($('#hTotalSprice').val());
var currPrice = parseFloat($('#hTotalPrice').val());
var currDiscount = parseFloat($('#hTotalDiscount').val());
var currDeposit = parseFloat($('#hTotalDeposit').val());

currSprice += standardprice;
currPrice += price;
currDiscount += discount;
currDeposit += deposit;

$('#lblTotalSprice').text('$'+addCommas(currSprice.toFixed(2)));
$('#lblTotalPrice').text('$'+addCommas(currPrice.toFixed(2)));
$('#lblTotalDiscount').text('$'+addCommas(currDiscount.toFixed(2)));
$('#lblTotalDeposit').text('$'+addCommas(currDeposit.toFixed(2)));

$('#hTotalSprice').val(currSprice.toFixed(2));
$('#hTotalPrice').val(currPrice.toFixed(2));
$('#hTotalDiscount').val(currDiscount.toFixed(2));
$('#hTotalDeposit').val(currDeposit.toFixed(2));
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
gandjyar
  • 1,145
  • 2
  • 10
  • 15
  • 1
    Since 0.5 is exactly halfway between 0 and 1 and rounding up is only a convention, I wonder how important it really is to guarantee a specific result. On the other hand, in order to test your code, you need predictable results and testing is important, so that's a good reason. – David Winiecki May 09 '14 at 17:44
  • 2
    Here's a hint as to why the rounding of `.toFixed` can seem unintuitive: `(0.1).toFixed(20)` . (Note that IE's implementation gives the "intuitive" result, while other browsers give the standards-compliant value.) – Noyo May 17 '15 at 20:30
  • 1
    My answer for similar question here: http://stackoverflow.com/a/37751946/2261514 – Pawel Jun 10 '16 at 15:45

25 Answers25

46

I have yet to find a number that toFixed10 does wrong. Can anybody else?

Thanks to blg and his answer which pointed me to Mozilla's toFixed10() method.

Using that I came up with this short one liner, which indeed covers all cases mentioned here...

function toFixed( num, precision ) {
    return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision);
}
filhit
  • 2,084
  • 1
  • 21
  • 34
user2823670
  • 537
  • 4
  • 6
  • 2
    What/where is toFixed10? Please add a link to it? – ledlogic Mar 13 '15 at 01:20
  • 10
    Number 0.0000001 or less results in NaN – Greenlandi Jul 28 '15 at 06:33
  • 1
    @ledlogic `toFixed10` looks like it was added by blg, it just calls `round10` and then `toFixed`. – Hanna Mar 24 '16 at 20:17
  • 5
    Nope, this is still imprecise, `toFixed(89.684449, 2)` should be `89.69`, but is `89.68`. – mrts Jan 01 '18 at 11:02
  • 2
    This doesn't handle double rounding. This is answer works https://stackoverflow.com/a/43998255/304894 – Codler Feb 23 '18 at 15:35
  • 18
    @mrts toFixed(89.684449, 2) is correctly 89.68, it would be only 69 if it was 89.685 – ed' Mar 06 '18 at 16:26
  • 6
    On `toFixed(0.000000015, 8)` get NaN. For correct answer 0.00000002 I use this function `function round(v,d){return parseFloat(Math.round(v.toFixed(d+1)+'e'+d)+'e-'+d)};` – Geograph Apr 25 '18 at 12:26
  • That toFixed10() link is broken, not archived, and [the history for that wiki page](https://github.com/mdn/content/commits/main/files/en-us/web/javascript/reference/global_objects/math/round) (now hosted on Github) doesn't go far back enough. It looks like the [JSFiddle associated with that other answer](https://jsfiddle.net/cCX5y/2/) is still up though. – Adam Katz Apr 26 '22 at 17:27
  • @Codler Funnily enough the second comment on that answer points out a failing case. – Charles Wood Jun 07 '22 at 15:12
28

I made this to use in all financial data as a best rounding function. You can test it on all problematic numbers. Javascript allows some sort of precision, so I used it to make almost every number be rounded as expected.

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

        var multiplicator = Math.pow(10, digits);
        n = parseFloat((n * multiplicator).toFixed(11));
        return Math.round(n) / multiplicator;
    }
Shura
  • 507
  • 5
  • 8
24

In Chrome, toFixed() rounds:

859.385 ==> 859.38
859.386 ==> 859.39

When I look at the ECMAScript 5th edition specification for .toFixed() (section 15.7.4.5), I do not see it explicitly describe rounding though it does describe something fairly obtusely that may be what Chrome has implemented.

It appears to me that if you want to control it with explicit rounding, then you should probably use the oft-suggested workaround of:

var roundedNum = (Math.round( num * 100 ) / 100).toFixed(2);

This will guarantee that you get predictable rounding like you are used to.

Working demo here: http://jsfiddle.net/jfriend00/kvpgE/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 20
    It'll **normally** guarantee that you get predictable rounding. However, as mentioned [in this answer](http://stackoverflow.com/questions/7406494/javascript-multiplying-incorrectly-causing-incorrect-rounding) the way javascript handles decimals can't really be trusted. Just try 35.855*100 in the Chrome console... I know, I was shocked too! This will mean that `(Math.round( 35.855 * 100 ) / 100).toFixed(2) == 35.85` rather than 35.86. See that other answer for tips... – mattbilson Aug 08 '13 at 15:33
  • 3
    @mattbilson This is standard IEEE floating point and doesn't mean JavaScript is untrustworthy in this regard. Floating point numbers just aren't the real numbers (mathematical sense) but are entirely predictable. – idmean Jan 20 '21 at 13:05
  • This is not a perfect answer. If you input 1.005, you'll get 1.00 but it should be 1.01. – foxiris Aug 01 '22 at 08:54
  • 1
    @foxiris - Yep, due to the wonders of floating point, `1.005 * 100` comes out to be `100.49999999999999` which derails the rest of the algorithm. – jfriend00 Aug 02 '22 at 02:50
20

Since in javascripts' toFixed-function, the floating point number5 does not belong to the upper half of an integer, the given number is rounded down if you have numbers like these:

859.385.toFixed(2) // results in 859.38

for a matter of fact you might append trailing floating point numbers (except zero) like here:

859.3851.toFixed(2) // results in 859.39

Therefore developers tend to add numbers like 0.00000000001 in order for it to be rounded appropriate and to not accidentally change the value of the number.

So I came up with a function which adds such a number depending on how many digits you want your floating point number to be fixed:

    // both parameters can be string or number
    function toFixed(number, decimals) {
        const x = Math.pow(10, Number(decimals) + 2);
        return (Number(number) + (1 / x)).toFixed(decimals)
    }
    toFixed(859.385, 2) //results in 859.39
    toFixed(859.3844, 2) //results in 859.38
InsOp
  • 2,425
  • 3
  • 27
  • 42
  • 3
    Best answer I could find! Easy code to read and it even handle double rounding! – Codler Feb 23 '18 at 15:36
  • 9
    `toFixed(859.3844, 2)` gives `859.39`, but should be `859.38` – ed' Mar 06 '18 at 16:32
  • toFixed(.004, 2); == .00 ... not .01 – MLissCetrus May 12 '20 at 17:42
  • Usually we know what precision we want to round to. Let's say we want num.toFixed(2). Then adding .0001 to num gets the right answer from toFixed. While a general function is fine, it seems to me to be a bit of overkill, unless the number of desired digits is a variable -- an unlikely circumstance. – Betty Mock Jul 17 '20 at 13:36
  • 1
    The idea is good but there is a mistake. The added fraction is too big and because of it even `0.0046` is rounded to `0.01`. Changing the intermediate line to `var x = Math.pow(10, Number(decimals) + 2);` helps resolving the issue – devaga Jun 14 '23 at 07:46
17

toFixed was never rounding nor meant for it.

It barely converts a floating point (exponent, mantissa) into a fixed-point (integer, decimals) (see definition).

While doing this, it might look as if it's rounding, because the rounded number is the closest approximation to the original floating-point representation.

Sadly, it is widely spread that toFixed can be used for rounding... well no it cannot, even the name suggests has nothing to do with it.

If you need rounding plus the toFixed padding:

function round(num, precision) {
  var base = 10 ** precision;
  return (Math.round(num * base) / base).toFixed(precision);
}
estani
  • 24,254
  • 2
  • 93
  • 76
10

Another good number to try along with 35.855 is 1.005

I don't think Robert Messerle's solution handles 1.005

The rounding decimal example here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round$revision/1383484#Decimal_rounding converts numbers to exponential notation and seems to get better results.

I created a fiddle here http://jsfiddle.net/cCX5y/2/ that demos the native, Robert Messerle example above (called toFixedB) and the one from Mozilla docs (called toFixed10).

I have yet to find a number that toFixed10 does wrong. Can anybody else?

filhit
  • 2,084
  • 1
  • 21
  • 34
  • 2
    Good fiddle and link. I added user2823670's one-liner: http://jsfiddle.net/cCX5y/3/ – Slashback Jun 18 '14 at 19:09
  • 1
    Looks like [this answer](http://stackoverflow.com/a/5535472/573634) also works and doesn't involve all the extra code. – Hanna Mar 29 '16 at 17:06
  • 1
    All of them are imprecise, `toFixed10(89.684449, 2)` and others should be `89.69`, but is `89.68`. – mrts Jan 01 '18 at 11:05
  • 7
    I think you are double rounding. (https://en.wikipedia.org/wiki/Rounding#Double_rounding). Any number such as 1.2345 will round to 1.23 not 1.24 – theycallmethesloth Jan 02 '18 at 16:39
9
function roundup(num,dec){
    dec= dec || 0;
    var  s=String(num);
    if(num%1)s= s.replace(/5$/, '6');
    return Number((+s).toFixed(dec));
 }

 var n= 35.855
 roundup(n,2)

/* returned value: (Number) 35.86 */

kennebec
  • 102,654
  • 32
  • 106
  • 127
  • 2
    Try it with `35.855`. – T.J. Crowder Nov 07 '13 at 07:59
  • This is the only solution I've seen that will round predictably. – Sean the Bean Nov 21 '13 at 15:18
  • The only problem is, it doesn't work if one of the decimals is 5 and the number of decimals to round to is higher than the position of that 5, e.g. roundup(70.5, 2). This could easily be improved by changing the regular expression in the replace statement, to something like: s.replace(/(\d{n})5/, '$16'), with n being equal to the dec paraemter. We only need to change the 5 in the n+1th position behind the decimal point, when rounding to n decimals, am I right? – Mansiemans Feb 11 '14 at 12:06
9

toFixed() works correctly! The problem is, that 859.385 has no representation as float at a computer. It is scanned as nearest possible value = 859.384999999999991. And rounding this value to 2 digits is 859.38 and not 859.39.

This is the reasons, that many programming languages (especially old for commerce, e.g. COBOL) support BCD numbers (binary coded decimals), where each digit is coded by itself into 4 bits (like hex without using A-F).

A general solution for prices: Calculate in cent/penny and print NUMBER/100.

A note to other solutions (functions provided here): They may help for some numbers, but mostly fail for e.g. 859.38499999.

Wiimm
  • 2,971
  • 1
  • 15
  • 25
6

You can use the Math.round() to round the number. If you want to round to a specific decimal point you can employ a little math:

var result=Math.round(original*100)/100
jasonlfunk
  • 5,159
  • 4
  • 29
  • 39
  • 13
    Note that this is unreliable. Try it with `35.855`, for instance, and you'll end up with `35.85` (not `35.86`). – T.J. Crowder Nov 07 '13 at 07:58
  • noe that this was just an example. insert for the `100` `Math.pow(10, decimalpoint)` where `decimalpoint` is the numbers of floating point digits you want to round your number – InsOp May 16 '17 at 10:03
6

I ran into this same problem today, and even trying suggestions in other answers I found that I still did not receive the result I expected. Finally, as I'm using AngularJS for my current project when running into this, I figured I check if AngularJS has already solved the same kind of problem before and indeed they had. Here's the solution they use and it works perfectly for me:

function toFixed(number, fractionSize) {
    return +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
}

Found here: AngularJS filters.js source

Bodin
  • 69
  • 1
  • 3
  • 1
    This is identical to [this answer](http://stackoverflow.com/a/23560569/1532548) except AngularJS doesn't have the trailing `.toFixed()` – Steve Eynon Aug 26 '15 at 11:40
6

If you are looking to get a number as output, then consider the Math.round() technique in other answers.

But if you want to get a string as output, for presentation to a human, then often n.toLocaleString() is more helpful than n.toFixed().

Why? Because it will also add commas or periods to the head of large numbers, which humans use to read. For example:

var n = 1859.385

n.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})

// Produces  1,859.39  in USA (locale en-US)
// Produces  1 859,39  in France (locale fr-FR)
// Produces  1.859,39  in Germany (locale de-DE)

The spec says that when passing undefined as the first argument, the user's own locale will be used (as specified by the OS). Unfortunately, as the linked documentation shows, Mozilla uses the en-US locale in this situation, but it may comply with the spec in future.

joeytwiddle
  • 29,306
  • 13
  • 121
  • 110
4

I stumbled upon this wondering why Number.toFixed was behaving strangely. I see that the native function is unreliable, which is unfortunate. Looking over the answers out of curiosity, I see most* of them don't behave properly with the number 35.855 as T.J. Crowder graciously commented on every one.

Maybe this will answer your question.

function toFixed(n,precision) {
    var match=RegExp("(\\d+\\.\\d{1,"+precision+"})(\\d)?").exec(n);
    if(match===null||match[2]===undefined) {
        return n.toFixed(precision);
        }
    if(match[2]>=5) {
        return (Number(match[1])+Math.pow(10,-precision)).toFixed(precision);
        }
    return match[1];
    }

The regex splits your number into an array of strings such as in toFixed(35.855,2): ["35.855", "35.85", "5"]. If the last number (after the precision cutoff) is >=5, add Math.pow(10, -precision) to the trimmed number. This will add .01 if you're cutting off at 2 decimals, .002 at 3, so on and so forth.

I don't know if this is foolproof, since it still performs decimal math on floats which can be unpredictable. I can say it rounds 35.855 up to 35.86.

4

Joy twindle answer here should be the best answer instead of the so many hack arounds.

   x = 1859.385;
   x = x.toFixed(2);
   alert(x);

gives a wrong rounding off ie 1859.38 instead of 1859.39

 x = 1859.385;
 x = x.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
 alert(x);

gives a correct rounding off 1,859.39

The only problem is the result returned is a string with thousand comma separator and so cannot be used for calculations. Using a regex i got from stack overflow to remove the comma the final result is

 x = 1859.385;
 x = x.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
 x=x.replace(/\,/g,'');
 alert(x);

that now returns 1859.39 and can be used for calculations. The value before regex ie 1,859.39 can be used for html display while the un-formatted value 1859.39 can be used for calculations.

webs
  • 528
  • 5
  • 11
4

I know this is an old question, but why not doing something like this:

let TruncatedValueInString = ThenNumberYouWantToTruncate.toFixed(decPlaces + 1).slice(0, -1);
Zardaloop
  • 1,594
  • 5
  • 22
  • 43
4

Note: You may only need this .toFixed() solution for handling very large numbers

Other posted solutions work well for standard numbers (below 1e21)

If you want to handle very large numbers and very small fractions with toFixed with correct rounding and without loss of accuracy and without having to use libraries and with a self-contained function, one possible effective solution is to make use of the BigInt to avoid loss of accuracy due to internal javascript rounding.

The function below toFixed(number, [digits]) does this.

Just pass the large number as a string with the required number of rounding digits. Numbers will be rounded properly.

The concept is the common way to split the number into the whole part and the decimal part but use BigInt() to hold these 2 parts. Thereafter, round and process as necessary for both positive and negative numbers without using any of the javascript Math or number functions to avoid loss of accuracy.

As a bonus for very special cases where the number is in a very large 'e' notation, I have added the eToNumber() function I have posted here: https://stackoverflow.com/a/66072001/11606728 to do that as an inline process. This may be removed if you want to maintain shortcode and do not bother about such numbers. [comments are provided in the code to remove it].

I have included various test cases to test the different possibilities (some of which are a bit odd but nevertheless do occur).

This idea can be taken further for improvement.

Hope it is useful.

/****************************************************************************
* @function    : toFixed(number, [digits])
* @purpose     : Emulate toFixed() for large numbers and
                 large fractional numbers with correct rounding
                 by using the BigInt() built-in Object.
* @version     : 1.01
* @author      : Mohsen Alyafei
* @date        : 03 February 2021
* @param       : {number} [numeric or string] pass large numbers as a string
* @param       : {number} [optional digits]
*              : The number of digits to appear after the decimal point;
*              : this may be a value from 0 and above unlimited.
*              : If this argument is omitted or is negative, it is treated as 0.
*              : Handles large 'e' notation number using the eToNumber() function.digits
*              : See https://stackoverflow.com/a/66072001/11606728
* @returns     : A string representing the given number using fixed-point notation.
****************************************************************************/

function toFixed(num,digits) {
    if (!num && num!==0) return "Cannot read property of null or undefined"; // Can change it to throw Error
    digits<0 && (digits=0);
    digits=digits||0;
    num=eToNumber(num); // Delete this line and function below if larage 'e' notation number are not required
    let wh = (num+="").split((.1).toLocaleString().substr(1,1)), f = wh[1];
    wh = wh[0];
    let fr = (f=f||"0").substr(0,digits), fn = BigInt(fr), w = BigInt(wh), fl = (""+fn).length,
        lZeros = fr.length-fl, minus = wh[0]=="-", inc = (wh<0 || minus) ? BigInt(-1):BigInt(1);
    f[digits]>=5 && (fn+=BigInt(1));
    (fn+="").length > fl && (lZeros-=1);
    lZeros >=0 ? lZeros="0".repeat(lZeros):(fn=fn.substring(1), lZeros="",
               (fn ? w +=inc : ((f[digits]>=5) && (w+=inc))));
    fn = lZeros + fn; L = digits-fn.length;
    L && (fn+="0".repeat(L)); w==0 && minus && (w="-"+w);
    return w+(fn?".":"")+fn;
    }

    //---------------------- Extra Function if needed --------------------------------
    // Delete this function if large 'e' notation number are not required
    // Convert very large 'e' numbers to plain string numbers.
    //--------------------------------------------------------------------------------
    function eToNumber(num) {
        let sign="";
        (num+="").charAt(0)=="-" && (num=num.substring(1),sign ="-");
        let arr = num.split(/[e]/ig); if (arr.length<2) return sign+num;
        let dot=(.1).toLocaleString().substr(1,1), n = arr[0], exp = +arr[1];
        let w = (n=n.replace(/^0+/,'')).replace(dot,''),
          pos = n.split(dot)[1]? n.indexOf(dot)+exp : w.length+exp,
            L = pos-w.length,s=""+BigInt(w);
         w = exp>=0 ? (L>=0 ? s+"0".repeat(L):r()): (pos<=0 ? "0"+dot+"0".repeat(Math.abs(pos))+s:r());
        if (!+w) w=0; return sign+w;
        function r(){return w.replace(new RegExp(`^(.{${pos}})(.)`),`$1${dot}$2`)}}

//================================================
//             Test Cases
//================================================
let r=0; // test tracker
r |= test(35.855,2,"35.86");
r |= test(12.00000015,2,"12.00");
r |= test(35.855,5,"35.85500");
r |= test(35.855,4,"35.8550");
r |= test(1.135,2,"1.14");
r |= test(1.135,3,"1.135");
r |= test(1.135,4,"1.1350");
r |= test(1.135,8,"1.13500000");
r |= test(0.1545,3,"0.155");
r |= test(89.684449,2,"89.68");
r |= test("0.0000001",2,"0.00");
r |= test("0.9993360575508052",3,"0.999");
r |= test("0.999336057550805244545454545",29,"0.99933605755080524454545454500");
r |= test("1.0020739645577939",3,"1.002");
r |= test(0.999,0,"1");
r |= test(0.999,1,"1.0");
r |= test(0.999,2,"1.00");
r |= test(0.975,0,"1");
r |= test(0.975,1,"1.0");
r |= test(0.975,2,"0.98");
r |= test(2.145,2,"2.15");
r |= test(2.135,2,"2.14");
r |= test(2.34,1,"2.3");
r |= test(2.35,1,"2.4");
r |= test("0.0000001",2,"0.00");
r |= test("0.0000001",7,"0.0000001");
r |= test("0.0000001",8,"0.00000010");
r |= test("0.00000015",2,"0.00");
if (r==0) console.log("Tests 01. Standard fractional numbers passed");
//================================================
r=0; // test tracker
r |= test("1234567890123456789444.99",5,"1234567890123456789444.99000");
r |= test("1234567890123456789444.1445",3,"1234567890123456789444.145");
r |= test("1234567890123456789444.14451445144514451745",19,"1234567890123456789444.1445144514451445175");
if (r==0) console.log("Tests 02. Large fractional numbers passed");
//================================================
r=0; // test tracker
r |= test(100,2,"100.00");
r |= test(0,5,"0.00000");
if (r==0) console.log("Tests 03. Non-fractional numbers passed");
//================================================
r=0; // test tracker
r |= test(12345.6789,null,"12346");
r |= test(2.1234,null,"2");
r |= test(12345.6789,undefined,"12346");
r |= test(2.1234,undefined,"2");
r |= test(12345.6789,"","12346");
r |= test(0.1234,"","0");
r |= test(2.1234,"","2");
if (r==0) console.log("Tests 04. Undefined, Null, and Empty Digits passed");
//================================================
r=0; // test tracker
r |= test(1.1155,2,"1.12");
r |= test(1.255,2,"1.26");
r |= test(1.265,2,"1.27");
r |= test(1.275,2,"1.28");
r |= test(1.285,2,"1.29");
r |= test(1.295,2,"1.30");
r |= test(2.05,1,"2.1");
r |= test(2.15,1,"2.2");
r |= test(2.55,1,"2.6");
r |= test(2.65,1,"2.7");
r |= test(2.215,2,"2.22");
r |= test(2.315,2,"2.32");
r |= test(2.715,2,"2.72");
r |= test(2.815,2,"2.82");
r |= test(2.005,2,"2.01");
r |= test(2.105,2,"2.11");
r |= test(2.405,2,"2.41");
r |= test(2.505,2,"2.51");
r |= test(2.605,2,"2.61");
r |= test(2.905,2,"2.91");
r |= test(0.00155,4,"0.0016");
r |= test(2.55,1,"2.6");
r |= test(-2.35,1,"-2.4");
if (r==0) console.log("Tests 05. Correct rounding passed");
//================================================
r=0; // test tracker
r |= test(-1.125,2,"-1.13");
r |= test(-1.15,1,"-1.2");
r |= test(-1.15,1,"-1.2");
r |= test(-1.45,1,"-1.5");
r |= test(-1.65,1,"-1.7");
r |= test(-1.95,1,"-2.0");
r |= test(-2.34,1,"-2.3");
r |= test("-0.024641163062896567",3,"-0.025");
r |= test("-0.024641163062896567",16,"-0.0246411630628966");
r |= test("0.024641163062896567",16, "0.0246411630628966");
r |= test("-0.0246411630628965",16,"-0.0246411630628965");
if (r==0) console.log("Tests 06. Negative numbers rounding passed");
//================================================
r=0; // test tracker
r |= test(.135,2,"0.14");    // without whole part
r |= test(-.135,2,"-0.14");
r |= test("+35.855",2,"35.86");
r |= test("0.0",2,"0.00");
r |= test("-0",2,"-0.00");       //minus 0
r |= test("-0.0",5,"-0.00000");  // minus 0
r |= test("",5,"Cannot read property of null or undefined");        // empty string
r |= test(null,5,"Cannot read property of null or undefined");      //null
r |= test(undefined,5,"Cannot read property of null or undefined"); // undefined
if (r==0) console.log("Tests 07. Special test cases passed");
//================================================
r=0; // test tracker
r |= test(1.1234e1,2,"11.23");      //11.234
r |= test(1.12e2,2,"112.00");       //112
r |= test(-1.1234e2,2,"-112.34");   // -112.34
r |= test(-1.1234e2,4,"-112.3400"); // -112.34
r |= test(-1.1235e2,2,"-112.35");   // -112.35
r |= test(-1.1235e2,1,"-112.4");    // -112.4
if (r==0) console.log("Tests 08. Standard e notation numbers passed");
//================================================

r=0; // test tracker
r |= test("123456789123456789.111122223333444455556666777788889999e+10",16,"1234567891234567891111222233.3344445555666678");
r |= test("1.1235678944556677e2",20,"112.35678944556677000000");
r |= test("99.1235678944556677e2",20,"9912.35678944556677000000");
if (r==0) console.log("Tests 09. Large e notation numbers passed");
//================================================

if (r==0) console.log(`${"-".repeat(22)}\nAll Test Cases Passed.\n${"-".repeat(22)}`);

//================================================
//             Test function
//================================================
function test(n1,n2,should) {
let result = toFixed(n1,n2);
if (result !== should) {console.log(`Output   : ${result}\nShould be: ${should}`);return 1;}
}
Mohsen Alyafei
  • 4,765
  • 3
  • 30
  • 42
3

this might help

    tofix2Decimals=function(float){
        if(parseInt(float)==float)return float.toFixed(2);
        $decimals=/\.(\d+)/.exec(float)[1].length;
        $decimals=$decimals>=2?$decimals+1:3;
        float+=Math.pow(10,-$decimals);
        return float.toFixed(2);
    }
user995789
  • 279
  • 1
  • 4
  • 15
3

This happens due to JavaScript's Floating point representation.

Try this:

Number.prototype.round = function(digits) {
    digits = Math.floor(digits);
    if (isNaN(digits) || digits === 0) {
        return Math.round(this);
    }
    if (digits < 0 || digits > 16) {
        throw 'RangeError: Number.round() digits argument must be between 0 and 16';
    }
    var multiplicator = Math.pow(10, digits);
    return Math.round(this * multiplicator) / multiplicator;
}

Number.prototype.fixed = function(digits) {
    digits = Math.floor(digits);
    if (isNaN(digits) || digits === 0) {
        return Math.round(this).toString();
    }
    var parts = this.round(digits).toString().split('.');
    var fraction = parts.length === 1 ? '' : parts[1];
    if (digits > fraction.length) {
        fraction += new Array(digits - fraction.length + 1).join('0');
    }
    return parts[0] + '.' + fraction;
}

Usage:

var n = 859.385;
console.log(n.round(2)); // 859.39
console.log(n.fixed(2)); // 859.39
console.log(n.round(4)); // 859.385
console.log(n.fixed(4)); // 859.3850
Toxiro
  • 528
  • 1
  • 3
  • 12
2

I have got proper solution from Lam Wei Li

function round(number, precision) {
  var shift = function (number, exponent) {
    var numArray = ("" + number).split("e");
    return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + exponent) : exponent));
  };
  return shift(Math.round(shift(number, +precision)), -precision);
}

Test Result

round(1.050, 1); // expected 1.1 , result 1.1  (correct)
round(1.005, 2); // expected 1.01, result 1.01 (correct)

round(3456.3456,  3); // 3456.346
round(3456.3456,  2); // 3456.35
round(3456.3456,  1); // 3456.3
round(3456.3456,  0); // 3456
round(3456.3456, -1); // 3460
round(3456.3456, -2); // 3500
round(3456.3456, -3); // 3000

round(undefined, 1        ); // NaN
round(null     , 1        ); // NaN
round("a"      , 1        ); // NaN
round(1        , null     ); // NaN
round(1        , undefined); // NaN
round(1        , "a"      ); // NaN
Mehedee
  • 336
  • 1
  • 9
2

I wanted something concise and accurate with a good combination of readable and speed. After reading all the answers to this question, and this particularly helpful answer in a similar question, this is my solution.

const round = (numberToRound, digits = 0, toFixed = false) => {
  const precision = 10 ** digits;
  const n = numberToRound * precision * (1 + Number.EPSILON);
  const roundedNumber = Math.round(n) / precision;
  return toFixed ? roundedNumber.toFixed(digits) : roundedNumber;
};

// rounding by half
console.log(round(0.5));
console.log(round(-0.5));

// fixed decimals
console.log(round(0.5, 2), '( Type:',typeof round(0.5, 2), ')');
console.log(round(0.5, 2, true), '( Type:',typeof round(0.5, 2, true), ')');
console.log(round(-0.5, 2), '( Type:',typeof round(-0.5, 2), ')');
console.log(round(-0.5, 2, true), '( Type:',typeof round(-0.5, 2, true), ')');

// edge cases
console.log(round(1.005, 2) === 1.01);
console.log(round(-1.005, 2) === -1.01);
console.log(round(39.425, 2) === 39.43);
console.log(round(-39.425, 2) === -39.43);
console.log(round(1234.00000254495, 10) === 1234.000002545);
console.log(round(-1234.00000254495, 10) === -1234.0000025449);

// edge cases from other answer's comments.
console.log(round(859.385, 2));
console.log(round(859.3844, 2));
console.log(round(0.000000015, 8))
console.log(round(35.855, 2));

I don't love toFixed as a boolean parameter but it works for now.

GollyJer
  • 23,857
  • 16
  • 106
  • 174
2
function toFixed(num, decimals) {
    return (Math.round((num + Number.EPSILON) * Math.pow(10, decimals)) / Math.pow(10, decimals)).toFixed(decimals)
}

I've found a valid answer here -> Round to at most 2 decimal places (only if necessary)

Math.round((num + Number.EPSILON) * 100) / 100

You have to add Number.EPSILON to the rounded number.

2

The toFixed() method in JavaScript does not follow the standard rounding rules for numbers ending in .5. It uses the "round half up" strategy, which means it rounds up when the next digit is 5 or greater.

If you need to round numbers ending in .5 in a more consistent way, you can create a custom rounding function. Here's an example:

function roundNumber(number, decimalPlaces) {
  const factor = 10 ** decimalPlaces;
  return Math.round(number * factor) / factor;
}

const number = 3.145;
const roundedNumber = roundNumber(number, 2);

console.log(roundedNumber); // Output: 3.15

Alternatively, you can use toLocaleString() method to achieve this. Here's another example:

const number = 3.145;
const locales = window.navigator.language;
const options = { maximumFractionDigits: 2 };
const roundedNumber = Number(number.toLocaleString(locales, options));

console.log(roundedNumber); // Output: 3.15
Jason Jin
  • 1,739
  • 14
  • 21
0

Here is my solution, including 3 features.

  1. Correct round up.
  2. Display only in number. (Preventing Scientific Notation)
  3. Removes trailing zeros.

I combined @user2823670's answer and this together.

var roundUp = function(num, precision) {
    // Return '' if num is empty string
    if (typeof num === 'string' && !num) return '';

    // Remove exponential notation
    num = toPlainString(num);

    // Fixed round up
    var result = +((+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision));

    // Remove exponential notation (once again)
    result = toPlainString(result);

    return result;
};

var toPlainString = function(num) {
    return ('' + num).replace(/(-?)(\d*)\.?(\d+)e([+-]\d+)/,
        function(a, b, c, d, e) {
            return e < 0
                ? b + '0.' + Array(1 - e - c.length).join(0) + c + d
                : b + c + d + Array(e - d.length + 1).join(0);
        }
    );
}
wonsuc
  • 3,498
  • 1
  • 27
  • 30
  • The second function `toPlainString` does not work properly. Still, the following numbers do not convert 1e-7 it should be 0.0000001 but gives 0.00000001 i.e. with one more zero inserted. Similarly for 1e-8, 1e-9 and higher. – Mohsen Alyafei Feb 02 '21 at 22:31
  • change the regex to the following `replace(/(-?)(\d*)\.?(\d*)e([+-]\d+)/` to get it working. – Mohsen Alyafei Feb 02 '21 at 23:04
  • An alternative solution for converting 'e' notation numbers (keeping accuracy) for large numbers and large fractions above 1^21 can be found here https://stackoverflow.com/a/66072001/11606728 – Mohsen Alyafei Feb 06 '21 at 10:19
0

This worked for me - "hackish"

function customTofix(value, precision) {
    let decimalVal = 0;
    
    if (value !== null) {
        let appendValue = (((value - Math.floor(value)) !== 0) ? (precision <= (value.toString().split(".")[1].length || 0)) ? '1' : '' : '');
        decimalVal = parseFloat(value.toString() + appendValue).toFixed(precision)
    }

    return decimalVal
}
0

My workout:

For me, This was enough to include in common javascript like helpers.js

// eslint-disable-next-line no-extend-native
Number.prototype.toFixed = function (fractionDigits) {
    var precisionTens = Math.pow(10, fractionDigits || 0);
    return (Math.round(Number(this) * precisionTens) / precisionTens);
}

This will overwrite the native javascript toFixed() prototype function.

Amit Shah
  • 7,771
  • 5
  • 39
  • 55
0

I spent way too long on this.

export function parseFloat<TDefault>(nbr: string | number, defaultValue: TDefault): number | TDefault
export function parseFloat(nbr: string | number): number | null
export function parseFloat(nbr: string | number, defaultValue: any = null): number | null {
    if(nbr == null || nbr === '') return defaultValue
    const val = globalThis.parseFloat(nbr as string)
    return Number.isFinite(val) ? val : defaultValue
}

export function fullWide(n: number|string):string {
    return parseFloat(n,0).toLocaleString('en-US', {useGrouping: false, maximumFractionDigits: 20})
}

export function round(value: string | number, decimalDigits = 0): number {
    return +(`${Math.round(+(`${fullWide(value)}e${decimalDigits}`))}e${-decimalDigits}`)
}

Passes all these tests:

test('round', () => {
    expect(round(1.2345, 0)).toBe(1)
    expect(round(1.2345, 1)).toBe(1.2)
    expect(round(1.2345, 2)).toBe(1.23)
    expect(round(1.2345, 3)).toBe(1.235)
    expect(round(1.2345, 4)).toBe(1.2345)
    expect(round(3.141592653589793, 15)).toBe(3.141592653589793)
    expect(round(3.141592653589793, 13)).toBe(3.1415926535898)
    expect(round(12.345, -1)).toBe(10)
    expect(round(4_500, -3)).toBe(5000)
    expect(round(89.684449, 2)).toBe(89.68)
    expect(round(89.685, 2)).toBe(89.69)
    expect(round(0.000000015, 8)).toBe(0.00000002)
    expect(round(1e-20, 20)).toBe(1e-20)
    expect(round(1.5e-19, 20)).toBe(0.00000000000000000015)
    expect(round(1.5e-19, 19)).toBe(0.0000000000000000002)
    expect(round('1.5e-19', 19)).toBe(0.0000000000000000002)
    expect(round('bacon', 19)).toBe(0)
})
mpen
  • 272,448
  • 266
  • 850
  • 1,236