Не уверен, что я тут глупый. Возможно, что-то очевидное, но когда вы часами смотрите на одну и ту же проблему, это начинает сводить вас с ума.
Я делаю несколько расчетов с использованием PHP, все довольно просто.
У меня есть таблица продаж, скажем:
total, costs
424.53, 125
853.91, 125
Чтобы получить данные мне нужно …
gross = total - cost
vat = gross - ( gross / 1.2 )
profit = gross - vat
Мне нужно сгенерировать отчет, поэтому для каждой строки в базе данных продаж мне нужно выполнить цикл и выполнить вышеуказанные вычисления, чтобы получить нужные мне данные.
Если я добавлю сумму итога и сумму затрат, а затем вычислю брутто, НДС и прибыль выше, а также округляем НДС и прибыль к 2 десятичным пластинам, значения будут такими, как ожидалось.
Проблема в том, что я зацикливаюсь на каждой строке и вычисляю брутто, НДС и прибыль. Если я не округляю НДС и прибыль в каждой строке, а округляю итоговые итоги, они соответствуют значениям, к которым я добавляю sum(total)
а также sum(costs)
,
Но затем в отчете, который я генерирую, если я не округляю НДС и прибыль, они не отображаются с точностью до двух знаков после запятой, которые мне нужны.
Фактический код приведен ниже, но я уверен, что это скорее логика, чем код.
$sum = 0; // Test variable
foreach( .. as ... )
{
// Assigning $total and $cost
$gross = $total - $cost;
$data['profit'] = $gross;
// If I round this VAT so vat shows to two decimal points, $sum becomes off by some pence.
// If I don't round it but then round $sum after the loop, it matches the echo statement value which is the correct amount
$vat = $this->vat( $gross );
$data['vat'] = $vat;
$profit = $gross - $vat;
$data['net_profit'] = $profit;
$sum += $profit;
$array[] = $data;
}
echo "131547.82<br><br>";
echo $sum;
die;
Это проблема точности, вызванная использованием поплавков.
Когда вы делаете вычисления с чистым PHP, вы должны быть осторожны.
Вы можете столкнуться с глюками при сравнении двух поплавков.
Я бы предложил использовать некоторую вспомогательную функцию или объект валют / денег для работы с ними. Возможно, лучше использовать расширение PHP для математических операций, например, расширение PHP BCMath, которое имеет, например, функцию bcadd (). В любом случае, вот несколько помощников, которые вы можете использовать в цикле вычислений.
/**
* turn "string float" into "rounded float with precision 2"* (string) 123,19124124 = (float) 123.19
*
* @param type $string
*/
function formatNumber($string, $precision = 2)
{
return round((float) str_replace(',', '.', $string), $precision);
}
Существует также Sprintf: echo sprintf("%.2f", $a);
,
Эти два основаны на PHP NumberFormatter из международного расширения.
// 123,19 EUR = 123.19
function parseNumber($string_number)
{
$fmt = numfmt_create('de_DE', \NumberFormatter::DECIMAL);
return numfmt_parse($fmt, $string_number);
}
// 123.19 = 123,19 EUR
function numberFormat($value)
{
$f = \NumberFormatter::create("de_DE", \NumberFormatter::CURRENCY);
return $f->formatCurrency($value, 'EUR');
}
Для сравнения двух поплавков:
/**
* Do not check, that the numbers are exactly the same,
* but check that their difference is very small!
* A really small rounding error margin (epsilon) is expected.
* It's an equals check within defined precision.
*/
function compareFloats($a, $b)
{
return (abs(($a - $b) / $b) < 0.000001) ? true : false;
}
Других решений пока нет …