A float cannot hold 0.07 exactly, so adding it a hundred times does not produce 7. This is invisible on a single line item and unavoidable on an invoice total, a VAT breakdown or a reconciliation report that has to agree with a bank statement to the cent. bcmath does the arithmetic on decimal strings instead, at a scale you nominate.
$total = 0.0;
for ($i = 0; $i < 100; $i++) {
$total += 0.07;
}
var_dump($total === 7.0); // false
$total = '0.00';
for ($i = 0; $i < 100; $i++) {
$total = bcadd($total, '0.07', 2);
}
var_dump($total === '7.00'); // true
bcmul('19.99', '3', 2); // '59.97'
bccomp('0.30', '0.3', 2); // 0 — equal at scale 2
Every argument and every result is a string, which is what makes it exact and also what makes it easy to defeat: a value that has already been through json_decode(), a PDO column fetched as a float, or a stray arithmetic operator anywhere upstream has lost the precision before bcmath ever sees it. Watch bcdiv() in particular — it truncates at the scale rather than rounding, so a percentage split needs the rounding written out explicitly. It is also perhaps two orders of magnitude slower than native arithmetic, which is irrelevant for an order total and disqualifying inside a loop over a million rows. Where the currency has fixed minor units, storing integer cents and dividing once at the edge is simpler and faster; bcmath earns its keep when division, percentages and multi-currency scales are involved.