Reputation: 1086
I have the following code in PHP, where I've attempted to overcome the stated issue by type-casting the variables into Integers and also avoiding floating-point errors by multiplying all values by 100 before comparison in order remove the 2 decimal places.
However, the following code still evaluates the expression to true and colours the text in red instead of green but when I echo the two values of $eq_left and $eq_right, they are identical with no decimal point.
Here's the code:
$eq_left = (int) ($eq_bal_CurrentAssets*100) + ($eq_bal_NonCurrentAssets*100) ;
$eq_right = (int) ($eq_bal_Liabilities*100) + ($eq_bal_Taxation*100) + ($eq_bal_Equity*100) ;
if ($eq_left !== $eq_right) {
$color = 'red';
$diff = abs($eq_left - $eq_right);
} else {
$color = 'green';
}
echo "<div style=\"color: $color; font-weight:bold;\">\n";
echo " " . number_format(($eq_left/100),2,".",",") . " = " . number_format(($eq_right/100),2,".",",") . "<br />\n";
if ($diff) {
echo " Difference = " . number_format(($diff/100),2,".",",") . "\n";
}
echo "</div>\n";
echo $eq_left . " | " . $eq_right
Any ideas?
Upvotes: 2
Views: 396
Reputation: 26185
I agree with the recommendation against floating point if you want exact decimal fraction representation.
The reason is that many decimal fractions can only be approximated in float or double. They are based on binary, not decimal, fractions. In general, a rational number a/b, with no common factors in a and b, can be expressed exactly in a radix r representation if, and only if, all prime factors of b are also prime factors of b. For example, in decimal 1/5 is 0.2, but 1/3 is 0.333333333... In a binary system, 1/5 causes the same problem as 1/3 in decimal.
In your code, I suggest rounding to zero decimal places after doing the multiplication by 100. The (int) cast rounds towards zero, which is not what you need. If the input is even slightly less than a positive integer n, the result of the cast is n-1. The result of round is n.
The floating point representation of a decimal fraction that cannot be represented exactly may be either slightly lower or slightly higher than the original decimal fraction. If you start with e.g. 0.29, convert it to the nearest IEEE 754 64 bit float, and multiply by 100 you will actually get the floating point equivalent of 28.999999999999996447286321199499070644378662109375
Converting that to int with rounding towards zero gives 28, not 29. Rounding it to the nearest int would give 29.
Upvotes: 3
Reputation: 93805
Never use floating point numbers for money. Always store monetary values as integer cents. You store $5.40 as 540 and divide by 100 when you want to display. Floating point numbers cannot accurately represent the decimals that you think they are.
Here are some pages that discuss why floats as money is a terrible idea:
The problems that you are having are inherent with float representations of decimals. The only way to reliably get around them is to use integers.
Upvotes: 1