30

Got a math calculation problem.

$a = 34.56

$b = 34.55

$a do some calculation to get this figure

$b is doing rounding to the nearest 0.05 to get this figure

what happens is

$c = $b - $a

supposedly it be -0.01, but I echo out the $c, which shows -0.00988888888888

I try to use number_format($c, 2), but the output is 0.00,

how can I make sure $a and $b is exactly 2 decimals, no hidden number at the back.

in my php knowledge, number_format is only able to format the display, but the value is not really 2 decimal,

I hope I can get help from here. This really frustrated me.

Sandra
  • 374
  • 6
  • 17
Shiro
  • 7,344
  • 8
  • 46
  • 80
  • 1
    Investigate `precision` in php.ini for more sweeping, PHP-wide changes. However, be advised that you're dealing with *significant digits* there. – Jed Smith Oct 22 '09 at 02:40

8 Answers8

67

Try sprintf("%.2f", $c);

Floating point numbers are represented in IEEE notation based on the powers of 2, so terminating decimal numbers may not be a terminating binary number, that's why you get the trailing digits.

As suggested by Variable Length Coder, if you know the precision you want and it doesn't change (e.g. when you're dealing with money) it might be better to just use fixed point numbers i.e. express the numbers as cents rather than dollars

$a = 3456;

$b = 3455;

$c = $b - $a;

sprintf ("%.2f", $c/100.0);

This way, you won't have any rounding errors if you do a lot of calculations before printing.

aksu
  • 5,221
  • 5
  • 24
  • 39
Charles Ma
  • 47,141
  • 22
  • 87
  • 101
  • thanks for your answer, the only way is to format the output, it is not possible to just set it to 2 decimal, except using String. – Shiro Oct 22 '09 at 02:27
  • If you want to just set it to 2 decimals, you should be using fixed-point numbers, not floating-point. See Variable Length Coder's answer. – Carl Norum Oct 22 '09 at 23:35
  • I not really understand what does it means by "fixed-point" numbers, can you kindly provide some example? Thanks a lot! – Shiro Oct 23 '09 at 02:43
  • I guess by 'fixed point' he means to multiply any floating point numbers by the precision you want to make it into an integer. So for 2 decimal place numbers, instead of using a floating point to represent dollar amounts, use a integer to represent the amount in cents. – Charles Ma Oct 23 '09 at 03:43
17

Use round():

$c = round($b - $a, 2);

Note: you can also choose the rounding mode as appropriate.

Edit: Ok I don't get what sprintf() is doing that number_format() isn't:

$c = number_format($b - $a, 2);

vs

$c = sprintf("%.2f", $b - $a);

?

cletus
  • 616,129
  • 168
  • 910
  • 942
  • 1
    Probably nothing, but sprintf() is a lot more fun to use for guys like me who are accustomed to C. Also saves one writing `$foo = number_format($b - $a, 2) . " apples!"` and instead writing `$foo = sprintf("%.2f apples!", $b - $a)` -- much more elegant, in my opinion. – Jed Smith Oct 22 '09 at 02:38
  • 1
    Round rocks, maybe cause that's all I know for now, still learning. – Newb Oct 23 '09 at 02:53
5

You've run into one of the traps of floating point numbers; that they cannot always represent exact decimal fractions. If you want exact decimal values, you're better off using integers and then dividing by the precision you want at the end.

For example, if you're doing calculations in floats represeting Dollars (or your favorite currency), you may want to actually do your calculations in integer cents.

Variable Length Coder
  • 7,958
  • 2
  • 25
  • 29
  • can you please give an example how integer cents work? Not really get what you means. Thanks a lot for your comment. – Shiro Oct 22 '09 at 04:22
  • 1
    "Integer cents" means that, for example, the amount $24.95 should be stored not as a floating-point 24.95, but rather as the integer 2495. The amount $10.00 would be stored as the integer 1000. And so forth. – Robert L Feb 17 '10 at 05:36
5

You can very neatly sidestep all of these issues simply by using the bcmath library.

Just remember to read the documentation and be careful whether you are passing arguments as strings or as numeric datatypes.

Robert L
  • 1,963
  • 2
  • 13
  • 11
4

Native Calculation:

$a = 34.56;
$b = 34.55;
$c = $b - $a; // -0.010000000000005    

Works as expected (! use always BC functions for real number calculations, the issue is for all C based platforms):

$a = '34.56';
$b = '34.55';
$c = bcsub($b, $a, 4); // -0.0100    
  • To further iterate on this. Use strings to represent the data rather than float/double data types. Then use BCMath functions to calculate variables. To utilize sprintf('%1$s', bcadd($var1, $var2)); The same applies to databases data storage. http://php.net/manual/en/book.bc.php – Will B. Sep 05 '13 at 04:07
4

I also ran into this issue recently when doing calculations with floats. For example, I had 2 floats that when subtracted and formatted, the value was -0.00.

$floatOne = 267.58;
$floatTwo = 267.58;
$result = number_format($floatOne - floatTwo, 2);
print $result; //printed a string -0.00

What I did was:

$result = abs($floatOne - $floatTwo);// Made the number positive
print money_format('%i', $result); // printed the desired precision 0.00

In my solution I know that floatOne will never be less than floatTwo. The money_format function is only defined if the system has strfmon capabilities, Windows does not.

jtaz
  • 119
  • 3
  • 1
    Warning!!! This function has been DEPRECATED as of PHP 7.4.0, and REMOVED as of PHP 8.0.0. Relying on this function is highly discouraged. – MaZzIMo24 Nov 09 '22 at 21:47
3

If still somebody reach this page with similar problems where floating number subtraction causes error or strange values. I want to explain this problem with a bit more details. The culprit is the floating point numbers. And different operating systems and different versions of programming languages can behave differently.

To illustrate the problem, I will explain why with a simple example below.

It is not directly related to PHP and it is not a bug. However, every programmer should be aware of this issue.

This problem even took many lives two decades ago.

On 25 February 1991 this problem in floating number calculation in a MIM-104 Patriot missile battery prevented it intercepting an incoming Scud missile in Dhahran, Saudi Arabia, contributing to the death of 28 soldiers from the U.S. Army's 14th Quartermaster Detachment.

But why it happens?

The reason is that floating point values represent a limited precision. So, a value might not have the same string representation after any processing. It also includes writing a floating point value in your script and directly printing it without any mathematical operations.

Just a simple example:

$a = '36';
$b = '-35.99';
echo ($a + $b);

You would expect it to print 0.01, right? But it will print a very strange answer like 0.009999999999998

Like other numbers, floating point numbers double or float is stored in memory as a string of 0's and 1's. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them. There are many standards how they are stored.

Floating-point numbers are typically packed into a computer datum as the sign bit, the exponent field, and the significand or mantissa, from left to right....

Decimal numbers are not well represented in binary due to lack of enough space. So, uou can't express 1/3 exactly as it's 0.3333333..., right? Why we can't represent 0.01 as a binary float number is for the same reason. 1/100 is 0.00000010100011110101110000..... with a repeating 10100011110101110000.

If 0.01 is kept in simplified and system-truncated form of 01000111101011100001010 in binary,when it is translated back to decimal, it would be read like 0.0099999.... depending on system (64bit computers will give you much better precision than 32-bits). Operating system decides in this case whether to print it as it sees or how to make it in more human-readable way. So, it is machine-dependent how they want to represent it. But it can be protected in language level with different methods.

If you format the result, echo number_format(0.009999999999998, 2); it will print 0.01.

It is because in this case you instruct how it should be read and how precision you require.

Selay
  • 6,024
  • 2
  • 27
  • 23
0
Now you can use this 'round' function to get 2 decimal values.

$a = '34.5638';
$b = round($a,2)
34.56 // result