Por que o Magento armazena um delta de arredondamento ao calcular impostos

14

No modelo tax/Sales_Total_Quote_Tax, existe um método _deltaRound()que arredonda um preço. Ele adiciona um delta pequeno, para interromper o comportamento não determinístico ao arredondar 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;
}

Mas ele armazena um delta. Se não conseguir encontrar um delta armazenado, cria um. Por quê? Pelo alcatrão que posso dizer, isso leva a resultados diferentes com operações idênticas.

Digamos que temos um $pricede 3.595 e não temos um cache $delta. À medida que avançamos no método, obteremos $ delta = 0.000001. Em seguida, obtemos $price= 3.595001, que arredonda para 3,60, portanto, temos um novo $deltade -0,004999. E retornamos 3,60.

Exceto agora que temos um delta, então vamos fazê-lo novamente, com $price= 3.595. $price= 3.595 - 0.004999 = 3.590001

Se arredondarmos, obteremos 3,59. Respostas diferentes.

Parece-me que qualquer algoritmo de arredondamento usado deve pelo menos dar a mesma resposta toda vez que é executado com os mesmos argumentos, mas não desta vez.

Max Bucknell
fonte
BTW, encontrou o mesmo erro no Magento 2.2.2
TheKitMurkit

Respostas:

9

Eu tenho o Magento 1.8 no meu servidor e verifiquei o _deltaRound()método. Parece assim agora.

/**
 * 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;
}

Como você pode ver, se o _roundingDeltas()não estiver definido, ele assumirá zerocomo valor padrão. É apenas para perceber você. A equipe Magento pode ouvir sua dúvida. Eles resolveram seu problema silenciosamente. :)

EDITAR

Vamos analisar o uso dessa função, aplicando-a em um exemplo em tempo real. Suponha que eu tenha um produto no carrinho tributável. A quantidade que comprarei será 5. Após aplicar o imposto, o produto terá um preço de US $ 10,5356. Então esta é a minha situação

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.

Então agora vamos calcular o preço real que vai produzir nessa situação. Será

  Total =  10.5356 x 5 = 52.678

Agora vamos assumir que o magento não usa o _deltaRound()método Ele apenas arredonda o preço do produto até duas casas decimais e depois calcula o preço total. Nesse caso, o preço do produto será arredondado para, 10.54portanto, o preço total seria

  Total = 10.54 x 5 = 52.7

Agora, vamos supor que o magento esteja usando o _deltaRound()método e essa função realmente arredonda o preço do produto para duas casas decimais. Junto com isso, ele manterá um valor delta, que é de fato a diferença entre o preço real e o preço arredondado, será usado para calcular o preço arredondado posteriormente. Aquiinsira a descrição da imagem aqui

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

Isso significa que o _deltaRound()método realmente torna o arredondamento do preço do imposto mais preciso do preço real do imposto. Como você declarou, esse método retorna um valor arredondado diferente, dependendo do valor delta. Esse valor delta realmente torna o arredondamento de impostos mais preciso.

De acordo com isso, podemos concluir que, à medida que a quantidade aumenta, se não estivermos adotando esse método, ele produzirá uma grande diferença entre o valor arredondado e o valor real. Mas se usarmos esse método, o valor arredondado será o mais próximo possível do valor real.

Por padrão, o Magento arredonda para duas casas decimais. Este é o método responsável por arredondar duas casas decimais

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

Se definirmos para 4 ou algo assim, podemos aumentar ainda mais a precisão do arredondamento.

Nota: Esta é a minha abertura e visão geral. Pode ou não ser verdade. No entanto, parece preciso e lógico para mim.

Obrigado.

Rajeev K Tomy
fonte
Eu realmente odeio o codificado 2 emroundPrice
David Manners
@DavidManners: sim, está correto. Mas o magento usa _deltaRound()para superar a dificuldade em certa medida. Qualquer como é codificado. Ele vai certamente prodcuce algumas dificuldades em alguns casos
Rajeev K Tomy
1
Olhando para github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/…, o valor padrão ainda é 0,0001 no magento 1.9, o que resultou em um erro recorrente de cálculos tributários no frete
ProxiBlue
2

Info

Preço da rodada no Magento com base no delta anterior da operação de arredondamento.

app / code / core / Mage / Tax / Model / Sales / Total / Quote / Tax.php: 1392 app / code / core / Mage / Tax / Model / Sales / Total / Quote / 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;
}

Às vezes, isso pode causar um erro devido ao erro de cálculo do delta alto ( $this->_calculator->round($price)). Por exemplo, por esse motivo, alguns preços podem variar na faixa de ± 1 centavo .

Solução

Para evitar isso, você precisa melhorar a precisão do cálculo delta.

mudança

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

para

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

É necessário fazer alterações nos dois arquivos:

app / code / core / Mage / Tax / Model / Sales / Total / Quote / Tax.php: 1392 app / code / core / Mage / Tax / Model / Sales / Total / Quote / Subtotal.php: 719

Não modifique ou corte os arquivos principais! Faça uma reescrita!

A solução foi testada em diferentes versões do Magento 1.9.x, mas talvez isso funcione em versões anteriores.

PS

A roundPricefunção de alteração , como mostrado abaixo, pode resolver o problema do erro de arredondamento, mas pode causar outros (por exemplo, algumas plataformas exigem o arredondamento de até duas casas decimais).

app / code / core / Mage / Core / Model / Store.php: 995

public function roundPrice($price)
{
    return round($price, 4);
}
Victor S.
fonte