¿Por qué Magento almacena un delta de redondeo al calcular impuestos?

14

En el modelo tax/Sales_Total_Quote_Tax, hay un método _deltaRound()que redondea un precio. Agrega un pequeño delta, para detener el comportamiento no determinista al redondear 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;
}

Pero almacena un delta. Si no puede encontrar ese delta almacenado, lo inventa. ¿Por qué? Por lo que puedo decir, esto conduce a resultados diferentes con operaciones idénticas.

Digamos que tenemos un $price3.595 y no tenemos un caché $delta. A medida que avanzamos por el método, obtendremos $ delta = 0.000001. Entonces obtenemos $price= 3.595001, que se redondea a 3.60, por lo que tenemos un nuevo $deltade -0.004999. Y devolvemos 3.60.

Excepto que ahora tenemos un delta, así que hagámoslo de nuevo, con $price= 3.595. $price= 3.595 - 0.004999 = 3.590001

Que si redondeamos, obtenemos 3.59. Diferentes respuestas

Me parece que cualquier algoritmo de redondeo utilizado debería al menos dar la misma respuesta cada vez que se ejecuta con los mismos argumentos, pero no esta vez.

Max Bucknell
fuente
Por cierto,
encontré

Respuestas:

9

Tengo Magento 1.8 en mi servidor y he comprobado el _deltaRound()método. Se ve así ahora.

/**
 * 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 puede ver, si _roundingDeltas()no está configurado, toma zerocomo valor predeterminado. Es solo por darte cuenta. El equipo de Magento puede escuchar tu duda. Resolvieron tu problema en silencio. :)

EDITAR

Analicemos el uso de esta función aplicándola en un ejemplo en tiempo real. Supongamos que tengo un producto en el carrito que está sujeto a impuestos. La cantidad que voy a comprar será, 5. Después de aplicar el impuesto, el producto tiene un precio de $ 10.5356. Entonces esta es mi situación

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.

Así que ahora calculemos el precio real que se producirá en esta situación. Será

  Total =  10.5356 x 5 = 52.678

Ahora supongamos que magento no usa el _deltaRound()método. Simplemente redondea el precio del producto hasta dos decimales y luego calcula el precio total. En este caso, el precio del producto se redondeará 10.54y, por lo tanto, el precio total sería

  Total = 10.54 x 5 = 52.7

Ahora supongamos que magento está utilizando el _deltaRound()método y esta función realmente redondea el precio del producto a dos decimales. Junto con eso, mantendrá un valor delta, que de hecho es la diferencia entre el precio real y el precio redondeado, que se utilizará para calcular el precio redondeado más adelante. aquíingrese la descripción de la imagen aquí

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

Eso significa que el _deltaRound()método realmente hace que el redondeo de los precios impositivos sea más preciso al precio impositivo real. Como indicó, este método devuelve valores redondos diferentes que dependen del valor delta. Este valor delta hace que el redondeo de impuestos sea más preciso.

De acuerdo con esto, podemos concluir que, a medida que aumenta la cantidad, si no adoptamos este método, producirá una gran diferencia entre el valor redondeado y el valor real. Pero si usamos este método, nuestro valor redondeado se acercará lo más posible al valor real.

Magento por defecto redondea a dos decimales. Este es el método responsable del redondeo de dos decimales.

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

Si lo establecemos en 4 o algo así, podemos aumentar aún más la precisión del redondeo.

Nota: Esta es mi apertura y resumen. Puede o no puede ser cierto. Sin embargo, me parece preciso y lógico.

Gracias.

Rajeev K Tomy
fuente
Realmente odio los 2 codificadosroundPrice
David Manners
@DavidManners: sí, es correcto. Pero magento usa _deltaRound()para superar la dificultad en cierta medida. De todos modos está codificado. Definitivamente producirá algunas dificultades en algunos casos
Rajeev K Tomy
1
Mirando github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/… el valor predeterminado sigue siendo 0.0001 en magento 1.9, lo que nos ha resultado en un error de redondeo con cálculos de impuestos en el envío
ProxiBlue
2

Informacion

Precio redondo en Magento basado en la operación de redondeo anterior delta.

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

A veces, esto puede causar un error debido al error de cálculo delta alto ( $this->_calculator->round($price)). Por ejemplo, por esta razón, algunos precios pueden variar en el rango de ± 1 centavo .

Solución

Para evitar esto, debe mejorar la precisión del cálculo delta.

Cambio

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

a

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

Los cambios deben hacerse en ambos archivos:

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

¡No modifique ni piratee los archivos principales! ¡Haz una reescritura!

La solución se probó en diferentes versiones de Magento 1.9.x, pero tal vez esto funcione en versiones anteriores.

PD

La roundPricefunción de cambio , como se muestra a continuación, puede resolver el problema del error de redondeo, pero puede causar otros (por ejemplo, algunas plataformas requieren redondear hasta 2 decimales).

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

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