fogersp
@fogersp

Проблемы с точностью?

Добрый день. Написал скрипт для сбора стоимости звонков и записи их в CSV файлик.
Есть переменная $amount_nds - стоимость звонка с уже включенным НДС 18%. Данные о стоимости собираются из базы.
Я прохожу скриптом по таблицам и собираю стоимость каждого звонка и потом выгружаю в CSV-файл.
Проблема в том, что помимо стоимости с НДС, которая уже есть, мне нужно из этой стоимости вычислить для каждого звонка стоимость БЕЗ НДС. Соответственно, я в цикле вычисляю значение без НДС и кладу в переменную $amount ($amount = $amount_nds / 1.18).
Потом если подсчитать в Excel SUM() ячеек со значениями без НДС, то получается отклонение от нужной суммы ячеек на 2-3 копейки. Ну а значение с НДС соответственно все нормально.
Заметил вот что, если писать в csv не округленные до 2 значения, то в итоге SUM() становится верной (и это правильно же: сначала все складываем, а результат уже округляем до нужного). Но, принимающим этот отчет нужно чтобы все записи были уже округлены до 2 знаков после запятой (как рубли.копейки). И в таком случае у меня сумма без НДС получается неверной.

Вот банальный пример:
$query = $db->prepare("SELECT amount FROM table");
$query->execute();
$data = $query->fetchAll(PDO::FETCH_ASSOC);

$sum = 0; // общая сумма без НДС
$sum_nds = 0; // общая сумма с НДС

foreach ($data as $row) {
    $amount_nds = $row['amount']; // стоимость с НДС
    $amount = round($amount_nds / 1.18, 2); // Вычисляем сумму без НДС

    $sum = $sum + $amount;
    $sum_nds = $sum_nds + $amount_nds;

    file_put_contents("test.csv", $amount . ";" . $amount_nds . "\r\n", FILE_APPEND);
}

echo "---------------------------------\r\nСумма: $sum | Сумма с НДС: $sum_nds\r\n";


В итоге получился вывод:
---------------------------------
Сумма: 4125.98 | Сумма с НДС: 4868.98


А Сумма должна была быть 4126.25
И соответственно если я не округляю каждое значение, а лашь итоговое то все считается правильно. Но, мне нужно писать округленные до 2 данные. И чем больше записей тем больше разница.
Ведь есть верное решение. В чем секрет? Помогите

UPD
Из ответов понятно, что в базе данные о цене звонка НЕ ДОЛЖНЫ быть с типом float или double, как у меня.
Но это пишет биллинг. Я с этим ничего не поделаю.

Вопрос: получается мне ничего не остается, как жить с этой погрешностью в несколько копеек? Ведь, как бы я не обрабатывал числа с точкой, от этого их точность не улучшится? Прошу окончательного ответа.
  • Вопрос задан
  • 2415 просмотров
Решения вопроса 1
miraage
@miraage
Старый прогер
Никогда никогда никогда не храните суммы/цены во float!
Храните в int, а потом уже делите на 100, после всех операций.

// UPD

Разумно использовать DECIMAL(11,2) , если СУБД поддерживает.
Ответ написан
Пригласить эксперта
Ответы на вопрос 4
dnovikoff
@dnovikoff
Найдите бухгалтера, который объяснит вам как правильно считать в данной ситуации.
Ответ написан
Комментировать
@maxloyko
Перечитайте как работает round без 3 параметра и поймете что у вас накапливается ошибка с точностью .

Пример:
echo round(3.4);         // 3
echo round(3.5);         // 4

и с каждой итерацией она будет расти
Ответ написан
Writerim
@Writerim
Заполнить позже...
что будет если 4 / 3
~ 1.333333333333
Умножаем 1.333333333333 * 3
Получаем,округляем - 3.99
Вопрос. где еще копейка?
Как уже сказали "Храните в int, а потом уже делите на 100, после всех операций."
Ответ написан
@promka
Может быть стоит обратить внимание на так называемое Банковское округление (Стандарт IEEE 754).
PHP_ROUND_HALF_EVEN
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы