0

As I'm sure many of have experienced, I'm having a little issue with floating point math - I fixed the issue in JavaScript, however whilst it seems ok in PHP I wanted to make sure it could be relied upon as it's slightly different to the JS version.

JavaScript:

var item = 25.00;
var postage = 24.99;

((item + postage) * 100) // 4998.999999999999

Obviously I don't want this, I want 49.99, so I change it to this:

((item + postage) * 100).toFixed(0) // 4999 (string)

Then for good measure I change it to this:

provideFloat((book_price + shipping_cost) * 100).toFixed(0)) // 4999 (float)

function provideFloat(num) {
    return parseFloat(num.replace(/,/g, ''));
}

Now, I need to test against this number on the PHP side to see if they match, so I tried this (for example):

PHP:

$item = 25.00;
$postage = 24.99;

echo ((item + $postage) * 100); // 4999

So as you can see, a different result than JavaScript - can I rely on this being "right" for comparisons sake and not ending up with something like 4998.999999999999?

For interests sake I tried to apply a similar technique that I did with JavaScript with a similar approach that JS uses in their toFixed function, with:

$number = (($item + $postage) * 100);
$expo = pow(10,0);
$number = intval($number * $expo) / $expo;

echo $number; // 4998

...and I get 4998 whereas JS got 4999 - so should I stick with what actually got the 4999 result? Can this be relied upon?

..or do you have to go to extremes like this?

$res = (($item + $postage) * 100);
$res = (float) number_format($res, 0, '.','');
echo $res; // 4999

My use case is I need to turn floating point numbers (dollars) into cents and then compare them from client side to server end.

Brett
  • 19,449
  • 54
  • 157
  • 290
  • 1
    I haven't read through the whole question yet because it's very long but I see no mention of using the [`bcmath`](http://php.net/manual/en/book.bc.php) library. If accurate floating point arithmetic is required in your application then you should use the various `bcmath` functions and make sure to specify the precision scale. – MonkeyZeus Aug 29 '18 at 16:50
  • Additionally, you should use [`math.js`](http://mathjs.org/docs/datatypes/bignumbers.html) for accurate JS arithmetic. I have not verified but I am 99.9899999999999948840923025273% (PHP 98.99+1) positive that your solution will fail gloriously at some point. – MonkeyZeus Aug 29 '18 at 16:55
  • Thanks for that - I mean I do want it to be accurate, but it's more about *both* sides being accurate / returning the same results. – Brett Aug 29 '18 at 16:55
  • @MonkeyZeus Ha...... exactly why I asked to be clear if what I was currently doing could be relied upon or not. – Brett Aug 29 '18 at 16:57
  • Floating point accuracy has been discussed quite extensively on Stack Overflow. See https://stackoverflow.com/q/21895756/2191572 or try googling `floating point issue site:stackoverflow.com` – MonkeyZeus Aug 29 '18 at 16:58
  • @MonkeyZeus That is how I originally cane across the `tofixed()` solution. :) – Brett Aug 29 '18 at 17:00
  • 1
    I am sad to see that you've ignored everything else discussed in those posts. I have not verified but the `.toFixed()` solution is unreliable because it rounds the number after the floating point arithmetic has been performed so you are rounding and incorrect result. Your "*solutions*" revolve around formatting the result and not the accuracy of the calculation. Garbage in is garbage out; I don't care how much lipstick you smear on that pig. – MonkeyZeus Aug 29 '18 at 17:02
  • toFixed is converting to a string anyways. Math.round would be a better solution. – Devon Bessemer Aug 29 '18 at 17:05
  • @MonkeyZeus I didn't read the answers in-depth so I didn't actually *ignore* anything; I actually thought using `toFixed` was a little odd, but multiple people were suggesting it so I went with it. – Brett Aug 29 '18 at 17:09
  • Like I said, it's gonna bite you hard one of these days and you will go nuts trying to figure out what bit you. – MonkeyZeus Aug 29 '18 at 17:14

1 Answers1

1

Simple answer: No. You can't rely on floating point precision in PHP, or any language. While it may work for this example, there will be cases where it will not match.

If precision is required, either use integers for math or use bcadd() with strings.

If close enough is fine, you should be able to get away with round(). number_format() is not desirable for comparisons, only formatting (see thousand separator).

Devon Bessemer
  • 34,461
  • 9
  • 69
  • 95
  • So you mean use round on the result of `(item + postage) * 100` for both JS and PHP? I just need to turn the numbers into full cents, no decimals and what you would *expect* the result to be. – Brett Aug 29 '18 at 17:06
  • Yes, while not precise, rounding to a whole number in both languages will probably be sufficient for your use case. – Devon Bessemer Aug 29 '18 at 17:07