Math.round(num) vs num.toFixed(0) and browser inconsistencies

83,494

Solution 1

Edit: To answer your edit, use Math.round. You could also prototype the Number object to have it do your bidding if you prefer that syntax.

Number.prototype.round = function() {
  return Math.round(this);
}
var num = 3.5;
alert(num.round())

I've never used Number.toFixed() before (mostly because most JS libraries provide a toInt() method), but judging by your results I would say it would be more consistent to use the Math methods (round, floor, ceil) then toFixed if cross-browser consistency is what you are looking for.

Solution 2

To address your two original issues/questions:

Math.round(num) vs num.toFixed(0)

The issue here lies in the misconception that these should always give the same result. They are, in fact, governed by different rules. Look at negative numbers, for example. Because Math.round uses "round half up" as the rule, you will see that Math.round(-1.5) evaluates to -1 even though Math.round(1.5) evaluates to 2.

Number.prototype.toFixed, on the other hand, uses what is basically equivalent to "round half away from zero" as the rule, according to step 6 of the spec, which essentially says to treat negatives as positive numbers, and then add back the negative sign at the end. Thus, (-1.5).toFixed(0) === "-2" and (1.5).toFixed(0) === "2" are true statements in all spec-compliant browsers. Note that these values are strings, not numbers. Note further that both -1.5.toFixed(0) and -(1.5).toFixed(0) are === -2 (the number) due to operator precedence.

Browser inconsistencies

Most modern browsers—or at least the ones you might be expected to support at the time of this writing except for IE—should all implement the specs correctly. (According to Renee's comment, the toFixed issue you pointed out in Opera has been fixed, presumably since they started using the same JS engine as Chrome.) It's still worth reiterating that, even if the specs were implemented consistently across all browsers, the behavior defined in the spec, particularly for toFixed rounding, can still be a bit unintuitive for "mere mortal" JS developers who expect true mathematical accuracy—see Javascript toFixed Not Rounding and this "works as intended" bug that was filed on the V8 JS engine for examples.

Conclusion

In short, these are two different functions with two different return types and two different sets of rules for rounding.

As others have suggested, I would also like to say "use whichever function fits your particular use case" (taking special care to note the peculiarities of toFixed, especially IE's errant implementation). I would personally lean more towards recommending some explicit combination of Math.round/ceil/floor, again, as others have mentioned. Edit: ...though, after going back and reading your clarification, your use case (rounding to a whole number) definitely calls for the aptly-named Math.round function.

Solution 3

I think FF is doing the right thing with toFixed, since step 10 below says "If there are two such n, pick the larger n."

And as Grant Wagner said: Use Math.ceil(x) or Math.floor(x) instead of x.toFixed().

Everything below is from the ECMAScript Language Specification:

15.7.4.5 Number.prototype.toFixed (fractionDigits)

Return a string containing the number represented in fixed-point notation with fractionDigits digits after the decimal point. If fractionDigits is undefined, 0 is assumed. Specifically, perform the following steps:

  1. Let f be ToInteger(fractionDigits). (If fractionDigits is undefined, this step produces the value 0).
  2. If f < 0 or f > 20, throw a RangeError exception.
  3. Let x be this number value.
  4. If x is NaN, return the string "NaN".
  5. Let s be the empty string.
  6. If x ≥ 0, go to step 9.
  7. Let s be "-".
  8. Let x = –x.
  9. If x ≥ 10^21, let m = ToString(x) and go to step 20.
  10. Let n be an integer for which the exact mathematical value of n ÷ 10^f – x is as close to zero as possible. If there are two such n, pick the larger n.
  11. If n = 0, let m be the string "0". Otherwise, let m be the string consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
  12. If f = 0, go to step 20.
  13. Let k be the number of characters in m.
  14. If k > f, go to step 18.
  15. Let z be the string consisting of f+1–k occurrences of the character '0'.
  16. Let m be the concatenation of strings z and m.
  17. Let k = f + 1.
  18. Let a be the first k–f characters of m, and let b be the remaining f characters of m.
  19. Let m be the concatenation of the three strings a, ".", and b.
  20. Return the concatenation of the strings s and m.

The length property of the toFixed method is 1.

If the toFixed method is called with more than one argument, then the behaviour is undefined (see section 15).

An implementation is permitted to extend the behaviour of toFixed for values of fractionDigits less than 0 or greater than 20. In this case toFixed would not necessarily throw RangeError for such values.

NOTE The output of toFixed may be more precise than toString for some values because toString only prints enough significant digits to distinguish the number from adjacent number values. For example, (1000000000000000128).toString() returns "1000000000000000100", while (1000000000000000128).toFixed(0) returns "1000000000000000128".

Solution 4

toFixed() returns a string value. From Javascript: The Definitive Guide

Converts a number to a string that contains a specified number of digits after the decimal place.

Math.round() returns an integer.

Clearly, toFixed() seem to be more use for money, for example,

'$' + 12.34253.toFixed(2) = '$12.34'

It seems a big pity that toFixed() does not appear to round properly!

Solution 5

Instead of toFixed(0) use Math.ceil() or Math.floor(), depending on what is required.

Share:
83,494
eft
Author by

eft

Updated on July 14, 2022

Comments

  • eft
    eft almost 2 years

    Consider the following code:

    for (var i=0;i<3;i++){
       var num = i + 0.50;
       var output = num + " " + Math.round(num) + " " + num.toFixed(0);
       alert(output);
    }
    

    In Opera 9.63 I get:

    0.5 1 0

    1.5 2 2

    2.5 3 2

    In FF 3.03 I get:

    0.5 1 1

    1.5 2 2

    2.5 3 3

    In IE 7 I get:

    0.5 1 0

    1.5 2 2

    2.5 3 3

    Note the bolded results. Why are this inconsistencies present? Does this mean that toFixed(0) should be avoided? What's the correct way to round a number to the nearest integer?

  • pettys
    pettys about 9 years
    Your Conclusion section, and the question it links to, seem to contradict the rest of your answer.
  • Noyo
    Noyo about 9 years
    I clarified my conclusion and answer, which should agree with the linked question's top answers. toFixed isn't buggy, just very unintuitive.
  • pettys
    pettys about 9 years
    Is the toFixed spec ambiguous? I'm trying to understand how different results between IE 11 and Chrome 42 is not a bug. On IE, 1.555.toFixed(2) yields "1.56" while on Chrome it yields "1.55". The top answer to the linked SO question starts out, "JavaScript's .toFixed() function is extremely buggy." The reason I'm taking the time to spell these out is because your statement, "the specs are implemented consistently across [modern] browsers," is either false or dangerously misleading.
  • Noyo
    Noyo about 9 years
    You're absolutely right about IE, @pettys--I only tested with the values given by the OP. After playing with some other values, it seems to be the only browser (that I've tested) that does something different/wrong. Updated and clarified my answer again--thanks!
  • Luca Steeb
    Luca Steeb about 9 years
    It's also important to mention that Math.round is incredibly faster: Math.round() is almost 500% faster than .toFixed (jsperf.com/math-round-vs-tofixed2).
  • Pacerier
    Pacerier about 7 years
    Gosh, had those ecmascript people ever worked on standards before?: That standard is awfully written and is an entire class all unto itself: Literally I've never seen any standard even coming close to this mess. If possible please rewrite the quoted text instead of bulkloading that spaghetti onto the answer.
  • stealththeninja
    stealththeninja almost 7 years
    To be fair, there are multiple methods of rounding: en.wikipedia.org/wiki/Rounding
  • JBallin
    JBallin over 6 years
    isn't it bad practice to modify native objects (i.e. Number)?
  • Julien
    Julien over 6 years
    I wholeheartedly agree @Pacerier. Do you happen to have examples of good specifications for reference?
  • Firephp
    Firephp almost 6 years
    Yes; in most cases, modifying the native implementations is considered bad for many reasons, mostly dealing with obscurity and the resulting unexpected behaviors, especially for other or future team members. I recommend extending the object you wish to modify by either mixin functions/factories or by writing your own custom 'type' that points to an instance to (in this case) Number.prototype.