16

In the model tax/Sales_Total_Quote_Tax, there is a method _deltaRound() that rounds a price. It adds a small delta, to stop nondeterministic behaviour when rounding 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;
}

But it stores a delta. If it cannot find such a stored delta, it makes one up. Why? As tar as I can tell, this leads to different results with identical operations.

Let's say we have a $price of 3.595, and we do not have a cached $delta. As we go through the method, we will obtain $delta = 0.000001. We then get $price = 3.595001, which rounds to 3.60, so we have a new $delta of -0.004999. And we return 3.60.

Except now we have a delta so let's do it again, with $price = 3.595. $price = 3.595 - 0.004999 = 3.590001

Which if we round, we get 3.59. Different answers.

It seems to me that any rounding algorithm used should at least give the same answer every time it is run with the same arguments, but not this time.

1
  • 1
    BTW, encountered the same error in Magento 2.2.2 Commented May 13, 2019 at 12:34

2 Answers 2

9

I have Magento 1.8 on my server and I have checked the _deltaRound() method. It look like this now.

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

As you can see, if the _roundingDeltas() is not set, it takes zero as its default value. Its just for noticing you. Magento Team may overhear your doubt. They solved your problem silently. :)

EDIT

Let us analyse the use of this function by applying it into a real time example. Suppose I have a product in cart which is taxable. The quantity that I am going to purchase be, 5. After applying tax, the prdouct has a price $10.5356. So this is my situation

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.

So now let us calculate the real price that is going to produce in this situation. It will be

  Total =  10.5356 x 5 = 52.678

Now let us assume, magento does not using _deltaRound() method. It just rounds product price upto two decimal places and then calculates total price. In this case, product price will get rounded to 10.54 and hence total price would be

  Total = 10.54 x 5 = 52.7

Now let us assume magento is using _deltaRound() method and this function actually rounds product price to two decimal. Along with that it will keep a delta value, which is in fact the difference between actual price and rounded price, will use for calculating rounded price later. Here enter image description here

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

That means _deltaRound() method actually make tax price rounding more accurate to the actual tax pricing. As you stated this method returns different round value depends upon the delta value. This delta value actually makes tax rounding more accurate.

According to this, we can conclude that, as quantity increases, if we are not adopting this method, it will produce a large difference between rounded value and actual value. But if we use this method, it will make our rounded value as close as possible with the actual value.

Magento by default rounds to two decimal places. This is the method that responsible for two decimal places rounding

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

If we set it to 4 or something, we can further increase the accuracy of rounding.

Note : This is my openion and overview. It may or may not be true. However it seems accurate and logical for me.

Thanks.

3
  • I really do hate the hard coded 2 in roundPrice Commented Jun 30, 2014 at 6:40
  • @DavidManners : yes it is correct. But magento uses _deltaRound() to overcome the difficulty by some extend. Any how its hard coded. It will definitely prodcuce some difficulties in some cases Commented Jun 30, 2014 at 6:43
  • 2
    Looking at github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/… the default value is still 0.0001 in magento 1.9, which has for us resulted in a rouning error ith tax calcualtions on shipping
    – ProxiBlue
    Commented Nov 20, 2017 at 3:43
3

Info

Round price in Magento based on previous rounding operation 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;
}

Sometimes this can cause an error due to the high delta calculation error ($this->_calculator->round($price)). For example, for this reason, some prices can vary in the range of ±1 cent.

Solution

To avoid this, you need to improve the accuracy of the delta calculation.

Change

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

to

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

Changes need to be made in both files:

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

Don't modify or hack core files! Do a rewrite!

The solution was tested on different versions of Magento 1.9.x, but maybe this will work in earlier versions.

P.S.

Change roundPrice function, as shown below, can solve the rounding error problem, but it can cause others (for example, some platforms require rounding up to 2 decimal places).

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

public function roundPrice($price)
{
    return round($price, 4);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.