1

I have this simple substraction code:

<?php
    $n1 = 257931.076;
    $n2 = 257930;

    echo $n1 - $n2;
?>

Why i got 1.0760000000009 instead of 1.076

Where did the 0000000009 came from? i need precise result and i don't want to use round() or number_format() because sometime i have more than 3 decimal, for example: 12345.678912, anyone know?

I have tried to use round() or number_format() but it only for fixed decimal point, not dynamic

faid
  • 375
  • 1
  • 4
  • 19
  • 3
    Start by [reading this](http://floating-point-gui.de/) if you want to know where the 0000000009 came from... with understanding how floating point maths works, you'll achieve wisdom – Mark Baker Feb 07 '18 at 09:23
  • Try to use `number_format($variable,2,",",".");` – proofzy Feb 07 '18 at 09:24
  • Then explain why `echo round($n1 - $n2, 10);` isn't precise enough for what you want; and only then (if you absolutely must have the absolute precision because somebody's life depends on it) consider the overhead of [gmp](http://php.net/manual/en/class.gmp.php) – Mark Baker Feb 07 '18 at 09:27
  • Floating point numbers are not guaranteed to be perfectly accurate because there are some fractions you simply can't represent perfectly in base 2 floating point (just like you can't perfectly represent 1 / 3 in base 10 decimal). Your best bet is just to do all your calculations and round them to the precision you need for display at the point of output – GordonM Feb 07 '18 at 09:29
  • so it's mean i can't do anything? okay then i'll create a function to fix this by count the number behind comma then `round()` it – faid Feb 07 '18 at 09:35
  • 2
    @faid That would be BAD! Just do all your calculations internally as you would normally, then round the final result for output. Doing anything else will simply be a hack. – GordonM Feb 07 '18 at 09:39
  • 2
    If I give you a piece of paper containing the result of me computing `1/3` and ask you to multiply that by 3 you can never get 1 unless I've given you an infinite length paper. This is the same problem but in base 2 – apokryfos Feb 07 '18 at 09:46

2 Answers2

2

As @GordonM perfectly explained it, you cannot expect exact results when using floating point values.

You can use a library such as brick/math to perform exact calculations on decimal numbers of any size:

use Brick\Math\BigDecimal;

$n1 = BigDecimal::of('257931.076'); // pass the number as a string to retain precision!
$n2 = 257930;

echo $n1->minus($n2); // 1.076

The library uses GMP or BCMath internally when available, but can work without these extensions as well (with a performance penalty).

BenMorel
  • 34,448
  • 50
  • 182
  • 322
1

For technical reasons that every programmer should be aware of, IEEE floating point numbers simply can't represent numbers precisely and will use the closest approximation they can when storing them (In fact the only fractions that can be stored perfectly have denominators that are powers of 2 (1/2, 1/4, 1/8, 1/16, etc. All other values are approximations). PHP has an ini value called "precision", which controls how many digits are considered significant WHEN OUTPUTTING floating point values. It defaults to 14, with any digits after that hidden.

However, the actual value stored may try to approximate the desired value with far more digits than that. If you change precision, you'll see what is really being stored.

php > $test = 0.1;
php > var_dump ($test);
php shell code:1:
double(0.1)
php > ini_set("precision", 100);
php > var_dump ($test);
php shell code:1:
double(0.1000000000000000055511151231257827021181583404541015625)
php > var_dump (0.25);
php shell code:1:
double(0.25)
php > var_dump (0.4);
php shell code:1:
double(0.40000000000000002220446049250313080847263336181640625)

What can you actually do about this? Not a great deal, this is just a consequence of how floating point works. You can try to avoid using floating point if you need exact values (for example when dealing with money amounts, store 3.99 as 399 pennies/cents instead of 3.99 pounds/dollars), or you can use the "bugnum" libraries that are available in PHP, GMP and BC_Math, but these are both tricky to use and have their own sets of gotchas. They can also be hard on storage and/or processor time. In most cases it's best to just live with it and be aware that when you're dealing with floating point you're not dealing with an exact representation.

GordonM
  • 31,179
  • 15
  • 87
  • 129