Working with Floating-Point Numbers in PHP
A while back we found an odd bug that only occurred with some inputs while doing calculations that involved decimals. When we performed a series of multiplications and additions and then compared our calculation with an expected result (using ==
) they didn’t show up as equal.
In this article, we’ll discuss why this is and what we can do to prevent it.
When Are Two Numbers Not Equal?
First, let’s look at what happens when we try to compare the result of an addition to our expected result.
Now we would expect $calculated
to be equal to $expected
but what happens if we try to actually compare them using the following code.
The output of this is as follows.
Odd right? So something must have happened in the addition to make our two numbers are different. Let’s perform a little echo
debugging.
Also weird…
Let’s look at two slightly different numbers
When is Subtraction Wrong?
Surely this is a problem with addition right? Let’s try some subtraction. Let’s say we have an application where we’re subtracting some amount of money (subtrahend) from a balance (minuend). This is an example where we really need to make sure we’re accurate.
Well, that’s not great but it’s not exactly what we expected. In the short run, this shouldn’t be a problem but what if we’re doing hundreds or thousands of calculations? This inaccuracy could get us into major trouble.
Why?
When we look at a number like 11.99
we can easily represent that number in our heads. But when PHP gets to 11.99
it has to determine a way to represent that number so PHP can use it later. It could store it as a string but then when we asked PHP to perform some kind of calculation on the number it would be slow. Because of that, PHP stores any number that has a decimal as what’s called a floating-point number.
Floating-point numbers allow us to store really tiny numbers (somewhere around 1x10^-300) to really large numbers (1.8x10^308). But we’re storing that number in 64-bits (for most environments these days) of information so it can’t keep all of the digits.
Computer science is all about trade-offs. So in order to get those 64-bits to hold as large of a number as possible, it’s actually holding two pieces of information. The first is the significand which is the leading part of the number and the second is an exponent. Because computers think in 2’s the significand is actually a series of sums of multiples of 1/2
. This allows us to represent a lot of numbers but not every possible number can be represented. This causes there to be some potential data loss when PHP stores the number.
Let’s look at an example.
Let’s say we trying to output the number “1,234,567,890.000001”
We’ll write the following script
When we run our script we get the following output.
In this case when PHP ran $number = 1234567890.000001;
it converted the number internally to 1.234567890000001x10^9
but then when it attempted to place this in memory there wasn’t enough “space” to store the 0.000001
so it truncated the number to 1.234567890x10^9
.
How Do We Fix This?
Now that we know what’s wrong, how do we fix our code?
Let’s look at how we can check to see if two floating-point numbers are equal. Instead of using the ==
operator, we’re going to calculate the absolute difference between the two numbers and then see if the difference is less than an allowed difference. This allowed difference is going to be very application dependent but we’ve found that 0.0001 is a good starting point as long as we’re not working with very small numbers in which case it will need to be much smaller.
Now when we run it we get what we expected.
For cases where we’re outputting a floating-point number, we should be rounding the value before displaying it so we don’t have a huge number of places after the decimal.
Finally, when working with currency the best solution is to either use a library specifically build to handle all these edge cases or just track the values using integers (in USD we would track the number of cents and no dollars).
Conclusion
Hopefully, this article has been helpful to you. If so please share it!
Scott Keck-Warren
Scott is the Director of Technology at WeCare Connect where he strives to provide solutions for his customers needs. He's the father of two and can be found most weekends working on projects around the house with his loving partner.
Top Posts
- Working With Soft Deletes in Laravel (By Example)
- Fixing CMake was unable to find a build program corresponding to "Unix Makefiles"
- Upgrading to Laravel 8.x
- Get The Count of the Number of Users in an AD Group
- Multiple Vagrant VMs in One Vagrantfile
- Fixing the "this is larger than GitHub's recommended maximum file size of 50.00 MB" error
- Changing the Directory Vagrant Stores the VMs In
- Accepting Android SDK Licenses From The OSX Command Line
- Fixing the 'Target class [config] does not exist' Error
- Using Rectangle to Manage MacOS Windows