Почему Magento сохраняет округленную дельту при расчете налогов

14

В модели tax/Sales_Total_Quote_Taxесть метод, _deltaRound()который округляет цену. Добавляет небольшую дельту, чтобы остановить недетерминированное поведение при округлении 0,5.

/**
 * Round price based on previous rounding operation delta
 *
 * @param float $price
 * @param string $rate
 * @param bool $direction price including or excluding tax
 * @param string $type
 * @return float
 */
protected function _deltaRound($price, $rate, $direction, $type = 'regular')
{
    if ($price) {
        $rate = (string)$rate;
        $type = $type . $direction;
        // initialize the delta to a small number to avoid non-deterministic behavior with rounding of 0.5
        $delta = isset($this->_roundingDeltas[$type][$rate]) ? $this->_roundingDeltas[$type][$rate] : 0.000001;
        $price += $delta;
        $this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);
        $price = $this->_calculator->round($price);
    }
    return $price;
}

Но это хранит дельту. Если он не может найти такую ​​сохраненную дельту, он составляет ее. Почему? Насколько я могу судить, это приводит к разным результатам с одинаковыми операциями.

Допустим, у нас есть $price3,595, и у нас нет кэшированного $delta. По мере прохождения метода мы получим $ delta = 0.000001. Затем мы получаем $price= 3,595001, что округляет до 3,60, поэтому мы получаем новое значение $delta-0,004999. И мы возвращаем 3,60.

За исключением того, что теперь у нас есть дельта, так что давайте сделаем это снова, с $price= 3,595. $price= 3,595 - 0,004999 = 3,590001

Что если мы округлим, мы получим 3,59. Разные ответы.

Мне кажется, что любой используемый алгоритм округления должен, по крайней мере, давать один и тот же ответ каждый раз, когда он запускается с теми же аргументами, но не в этот раз.

Макс Бакнелл
источник
Кстати, столкнулся с той же ошибкой в ​​Magento 2.2.2
TheKitMurkit

Ответы:

9

У меня есть Magento 1.8 на моем сервере, и я проверил _deltaRound()метод. Теперь это выглядит так.

/**
 * Round price based on previous rounding operation delta
 *
 * @param float $price
 * @param string $rate
 * @param bool $direction price including or excluding tax
 * @param string $type
 * @return float
 */
protected function _deltaRound($price, $rate, $direction, $type = 'regular')
{
    if ($price) {
        $rate  = (string) $rate;
        $type  = $type . $direction;
        $delta = isset($this->_roundingDeltas[$type][$rate]) ? $this->_roundingDeltas[$type][$rate] : 0;
        $price += $delta;
        $this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);
        $price = $this->_calculator->round($price);
    }
    return $price;
}

Как видите, если значение _roundingDeltas()не установлено, оно принимает zeroзначение по умолчанию. Это просто для того, чтобы заметить тебя. Команда Magento может подслушать ваши сомнения. Они решили твою проблему молча. :)

РЕДАКТИРОВАТЬ

Давайте проанализируем использование этой функции, применив ее в качестве примера в реальном времени. Предположим, у меня есть продукт в корзине, который облагается налогом. Количество, которое я собираюсь приобрести, составляет 5. После применения налога цена на продукт составляет 10,5356 долларов США. Так что это моя ситуация

CART
-------
   Product A
       - Price (including tax) - 10.5356
       - Quantity              - 5
       - Tax Rule  - Apply tax for each product. Then calculate the total price according to the quantity purchased.

Итак, теперь давайте посчитаем реальную цену, которую собираемся произвести в этой ситуации. Это будет

  Total =  10.5356 x 5 = 52.678

Теперь давайте предположим, что magento не использует _deltaRound()метод. Он просто округляет цену продукта до двух знаков после запятой, а затем вычисляет общую стоимость. В этом случае цена продукта будет округлена до, 10.54и, следовательно, общая цена будет

  Total = 10.54 x 5 = 52.7

Теперь давайте предположим, что magento использует _deltaRound()метод, и эта функция фактически округляет цену продукта до двух десятичных знаков. Наряду с этим он будет сохранять значение дельты, которое фактически является разницей между фактической ценой и округленной ценой, и будет использоваться для расчета округленной цены позже. Вотвведите описание изображения здесь

  Total =  10.54+10.53+10.54+10.53+10.54 = 52.68

Это означает, что _deltaRound()метод фактически делает округление налоговой цены более точным к фактической налоговой оценке. Как вы указали, этот метод возвращает различное значение округления, зависящее от значения дельты. Это значение дельты фактически делает округление налогов более точным.

Исходя из этого, мы можем сделать вывод, что по мере увеличения количества, если мы не примем этот метод, он приведет к большой разнице между округленным значением и фактическим значением. Но если мы используем этот метод, он сделает наше округленное значение как можно ближе к фактическому значению.

Magento по умолчанию округляет до двух знаков после запятой. Это метод, который отвечает за округление до двух знаков после запятой

Location :app/code/core/Mage/Core/Model/Store.php
public function roundPrice($price)
{
    return round($price, 2);
}

Если мы установим его на 4 или что-то еще, мы можем еще больше повысить точность округления.

Примечание: это мое открытие и обзор. Это может или не может быть правдой. Однако это кажется точным и логичным для меня.

Благодарю.

Раджив К Томи
источник
Я действительно ненавижу жестко закодированные 2 вroundPrice
Дэвид Мэннерс
@DavidManners: да, это правильно. Но magento использует, _deltaRound()чтобы преодолеть трудности в некоторой степени. Любой, как это жестко закодировано. Это определенно вызовет некоторые трудности в некоторых случаях
Rajeev K Tomy
1
Глядя на github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/… в magento 1.9 значение по умолчанию все еще равно 0,0001, что для нас привело к ошибке округления при расчете налогов при доставке
ProxiBlue
2

Информация

Цена раунда в Magento основана на предыдущей операции округления delta.

Приложение / код / ​​ядро ​​/ Маг / Налог / Модель / Продажи / Итого / Цитата / Tax.php: 1392 Приложение / Код / ядро ​​/ Маг / Налог / Модель / Продажи / Итого / Цитата / Subtotal.php: 719

protected function _deltaRound($price, $rate, $direction, $type = 'regular')
{
    if ($price) {
        $rate = (string)$rate;
        $type = $type . $direction;
        // initialize the delta to a small number to avoid non-deterministic behavior with rounding of 0.5
        $delta = isset($this->_roundingDeltas[$type][$rate]) ? $this->_roundingDeltas[$type][$rate] : 0.000001;
        $price += $delta;
        $this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);
        $price = $this->_calculator->round($price);
    }
    return $price;
}

Иногда это может вызвать ошибку из-за ошибки вычисления высокой дельты ( $this->_calculator->round($price)). Например, по этой причине некоторые цены могут варьироваться в диапазоне ± 1 цент .

Решение

Чтобы этого избежать, нужно повысить точность расчета дельты.

+ Изменить

$this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);

в

$this->_roundingDeltas[$type][$rate] = $price - round($price, 4);

Изменения необходимо внести в оба файла:

Приложение / код / ​​ядро ​​/ Маг / Налог / Модель / Продажи / Итого / Цитата / Tax.php: 1392 Приложение / Код / ядро ​​/ Маг / Налог / Модель / Продажи / Итого / Цитата / Subtotal.php: 719

Не изменяйте и не взламывайте основные файлы! Сделай переписать!

Решение было протестировано на разных версиях Magento 1.9.x, но, возможно, это будет работать в более ранних версиях.

PS

roundPriceФункция изменения , как показано ниже, может решить проблему ошибки округления, но может вызвать другие (например, некоторые платформы требуют округления до 2 десятичных знаков).

Приложение / код / ​​ядро ​​/ Mage / Ядро / Модель / Store.php: 995

public function roundPrice($price)
{
    return round($price, 4);
}
Виктор С.
источник