skip to Main Content

I think I tried all the examples from the net! This is my PHP code:

echo(number_format(22.95 * 4.1, 2));

The result is 94.10 with PHP. I have been trying to get the same result with JavaScript, but no matter what I try, its always 94.09. Any idea how to make that work?

Some of the code I tried:

var x = 22.95;
var y = 4.10;
var res = x * y;
var res1 = res.toFixed(3);
console.log(res);
console.log(res1);
console.log(Number(res1).toFixed(2));
console.log(Math.round(res * 100) / 100);

Thanks!

2

Answers


  1. You experiencing a floating point error on the JavaScript side.

    22.95*4.1 is not 94.095 (as you might expect) but 94.09499999999998 in (32bit) floating-point math, therefor rounded down to .09 and not up to .10.

    There’s no good way to fix this in JavaScript but you can force PHP to behave like JavaScript using ini_set('precision', 17).

    See answer from question Why can PHP calculate 0.1 + 0.2 when other languages fail?:

    PHP has a precision configuration value which sets the number of significant digits displayed in floating point numbers. It is 14 by default, which is the reason 0.1 + 0.2 is displayed as 0.3.

    If, however, you do this:

    ini_set('precision', 17);
    echo 0.1 + 0.2;
    

    you get 0.30000000000000004

    Login or Signup to reply.
  2. Albeit both, PHPs’ number_format() as well as JSs’ Number.prototype.toFixed() do rounding, the rounding is different between those two functions.

    In number_format() PHP rounds up if the sub-fraction is .5 or higher, and down if lower.

    number_format(22.95 * 4.1, 3); # "94.095"
    number_format(22.95 * 4.1, 2); # "94.10"
    
    (22.95 * 4.1).toFixed(3); // '94.095'
    (22.95 * 4.1).toFixed(2); // '94.09'
    

    To get the same result in JS with a precision of two digits:

    (Math.round(100 * (22.95 * 4.1).toFixed(3)) / 100).toFixed(2); // '94.10'
    

    To get the same result in PHP with a precision of two digits:

    substr(number_format(22.95 * 4.1, 3), 0, -1); # "94.09"
    

    Setting the precision like ini_set(‘precision’, 17); as suggested in a different answer by Niklas E., changed nothing to that for me. It was likely not too little for the numbers in the example that it could have had any effect.

    While there is floating point precision and it can be unexpected (floating point rounding errors, cf. What causes floating point rounding errors?), both PHP and JS are using the same standard for floating point numbers:

    PHP float "typically uses the IEEE 754 double precision format." (ref)

    JS "Number type is a double-precision 64-bit binary format IEEE 754 value" (ref)

    So the difference here can be explained by the rounding only. PHPs’ number_format() rounds "half up" while in JS it is also that way, however it looks like that .toFixed() only rounds "if necessary", whatever that means, perhaps only for the last digit.

    This makes not much sense for formatting numbers to users, e.g. many use-cases even when displaying only two digits, the internal rounding is for four digits (book keeping).

    four digits............................: 94.0949
    three digits rounded (from more digits): 94.095
    two digits rounded (from four digits)..: 94.10
    three digits...........................: 94.094
    two digits rounded (from three digits).: 94.09
    two digits.............................: 94.09
    

    So in the end this may show as a precision error, which may fall into the context of the other answer, and specifically for that .toFixed(n) has too little precision when rounding the internal value (or perhaps when deciding about the rounding, the description on MDN has something like "rounding when necessary").

    (Math.round(100 * (22.95 * 4.1).toFixed(3)) / 100).toFixed(2); // '94.10'
    (Math.round(100 * (22.95 * 4.1).toFixed(4)) / 100).toFixed(2); // '94.10'
    

    Here the .toFixed(3/4) rounds when necessary from four/five digits of the internal representation and Math.round() then creates a new number that is not prone any longer to the floating point precision errors of .toFixed(). The Math.round() results in a new number that is rounded for the precision of two digits, therefore divided by one hundred (ten times ten, two digits).

    Then .toFixed(2) can’t ruin that.

    A similar rounding can be done by first rounding for three/four digits, and then for two digits:

    Math.round((Math.round(10000 * (22.95 * 4.1)) / 100)) / 100; // 94.1
    Math.round((Math.round(1000 * (22.95 * 4.1)) / 10)) / 100;   // 94.1
    

    Using the .toFixed(2) function then will give the expected string:

    94.1.toFixed(2); // '94.10'
    

    Applying toFixed(1) first (comment by evolutionxbox) will make you loose precision you want to have in the format for display (much likely as you formatted the number with two digits).


    Wikipedia: Double-precision floating-point format

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search