<p>It is stored in 4 decimal places because some currencies have more than 2 decimal places.</p>

<p>We round before multiplication because we need don't store any value on the line item in a number of decimal places larger than the base currency. This allows us to add up line items without creating new cents.</p>

<p>As you suggest this could be changed to <code>CommerceCurrencyHelper::round($this-&gt;qty * $this-&gt;salePrice);</code> but this would be a breaking change for other users. We will consider it for Commerce 2.</p>

<p>I don't recommend using <code>&gt; 2</code> decimal places to accomplish your "12 for 6.99" offer, rather make a discount adjuster that reduces the regular price of line item with a discount if <code>qty modulo 12 = 0</code>.</p>

CC BY-SA 3.0
2018-01-11T09:57:50.733Z
Thanks for the response Luke. However as this is a production site with an order history, I'm not really a fan of changing all the pricing to fix this.
Jay
CC BY-SA 3.0
2018-01-11T04:16:06.853Z
2018-01-11T04:16:06.853Z
Luke Holder
<p>We've had this problem for a while now and it's largely gone un-noticed but I'd like to raise it.</p>

<p>We have a bunch of products that are discounted when you buy them in bulk. E.g. 12 products for 69.99. </p>

<p>In order to achieve this we calculate what the price should be for each product, by dividing 69.99 / 12, which is 5.8325.</p>

<p>We put this in the database as the product price and I can see it is stored in the database correctly.</p>

<p>However when you add 12 items to the cart, the total is 69.96 - anything past 2 decimal places is being rounded off prior to multiplication.</p>

<p>I've managed to work out a fix for this, but its not ideal as it will get wiped out if anyone ever updates the LineItemModel.</p>

<p>Here is my solution to change the calculation:</p>

<pre><code>public function getSubtotal()
{
    // The subtotal should always be rounded.
    return $this-&gt;qty * CommerceCurrencyHelper::round($this-&gt;salePrice);
}
</code></pre>

<p>to</p>

<pre><code>public function getSubtotal()
{
    // The subtotal should always be rounded.
    return CommerceCurrencyHelper::round($this-&gt;qty * $this-&gt;salePrice);// Modified: JL
}
</code></pre>

<p>and change</p>

<pre><code>public function fillFromPurchasable(Purchasable $purchasable)
{
    $this-&gt;price = $purchasable-&gt;getPrice();
    $this-&gt;taxCategoryId = $purchasable-&gt;getTaxCategoryId();
    $this-&gt;shippingCategoryId = $purchasable-&gt;getShippingCategoryId();

    // Since sales cannot apply to non core purchasables yet, set to price at default
    $this-&gt;salePrice = $purchasable-&gt;getPrice();
    $this-&gt;saleAmount = 0;

    $snapshot = [
        'price'         =&gt; $purchasable-&gt;getPrice(),
        'sku'           =&gt; $purchasable-&gt;getSku(),
        'description'   =&gt; $purchasable-&gt;getDescription(),
        'purchasableId' =&gt; $purchasable-&gt;getPurchasableId(),
        'cpEditUrl'     =&gt; '#',
        'options'       =&gt; $this-&gt;options
    ];

    // Add our purchasable data to the snapshot, save our sales.
    $this-&gt;snapshot = array_merge($purchasable-&gt;getSnapShot(), $snapshot);

    $purchasable-&gt;populateLineItem($this);

    //raising onPopulate event
    $event = new Event($this, [
        'lineItem' =&gt; $this,
        'purchasable' =&gt; $this-&gt;purchasable
    ]);
    craft()-&gt;commerce_lineItems-&gt;onPopulateLineItem($event);
    // Always make sure salePrice is equal to the price and saleAmount
    $this-&gt;salePrice = CommerceCurrencyHelper::round($this-&gt;saleAmount + $this-&gt;price);
}
</code></pre>

<p>to</p>

<pre><code>public function fillFromPurchasable(Purchasable $purchasable)
{
    $this-&gt;price = $purchasable-&gt;getPrice();
    $this-&gt;taxCategoryId = $purchasable-&gt;getTaxCategoryId();
    $this-&gt;shippingCategoryId = $purchasable-&gt;getShippingCategoryId();

    // Since sales cannot apply to non core purchasables yet, set to price at default
    $this-&gt;salePrice = $purchasable-&gt;getPrice();
    $this-&gt;saleAmount = 0;

    $snapshot = [
        'price'         =&gt; $purchasable-&gt;getPrice(),
        'sku'           =&gt; $purchasable-&gt;getSku(),
        'description'   =&gt; $purchasable-&gt;getDescription(),
        'purchasableId' =&gt; $purchasable-&gt;getPurchasableId(),
        'cpEditUrl'     =&gt; '#',
        'options'       =&gt; $this-&gt;options
    ];

    // Add our purchasable data to the snapshot, save our sales.
    $this-&gt;snapshot = array_merge($purchasable-&gt;getSnapShot(), $snapshot);

    $purchasable-&gt;populateLineItem($this);

    //raising onPopulate event
    $event = new Event($this, [
        'lineItem' =&gt; $this,
        'purchasable' =&gt; $this-&gt;purchasable
    ]);
    craft()-&gt;commerce_lineItems-&gt;onPopulateLineItem($event);
    // Always make sure salePrice is equal to the price and saleAmount
    $this-&gt;salePrice = $this-&gt;saleAmount + $this-&gt;price;// Modified: JL
}
</code></pre>

<p>Can anyone tell me if this is a known issue and whether there is any reason why the code is not like this to begin with? Why store values at 4dp if it's always rounded off to 2dp when calculated?</p>

<plugin-craftcommerce><rounding>
Rounding issue with Craft Commerce
# Rounding issue with Craft Commerce

(Asked by: username_0 on 2018-01-10)

We've had this problem for a while now and it's largely gone un-noticed but I'd like to raise it.
We have a bunch of products that are discounted when you buy them in bulk. E.g. 12 products for 69.99. 
In order to achieve this we calculate what the price should be for each product, by dividing 69.99 / 12, which is 5.8325.
We put this in the database as the product price and I can see it is stored in the database correctly.
However when you add 12 items to the cart, the total is 69.96 - anything past 2 decimal places is being rounded off prior to multiplication.
I've managed to work out a fix for this, but its not ideal as it will get wiped out if anyone ever updates the LineItemModel.
Here is my solution to change the calculation:
public function getSubtotal()
{
    // The subtotal should always be rounded.
    return $this->qty * CommerceCurrencyHelper::round($this->salePrice);
}

to
public function getSubtotal()
{
    // The subtotal should always be rounded.
    return CommerceCurrencyHelper::round($this->qty * $this->salePrice);// Modified: JL
}

and change
public function fillFromPurchasable(Purchasable $purchasable)
{
    $this->price = $purchasable->getPrice();
    $this->taxCategoryId = $purchasable->getTaxCategoryId();
    $this->shippingCategoryId = $purchasable->getShippingCategoryId();

    // Since sales cannot apply to non core purchasables yet, set to price at default
    $this->salePrice = $purchasable->getPrice();
    $this->saleAmount = 0;

    $snapshot = [
        'price'         => $purchasable->getPrice(),
        'sku'           => $purchasable->getSku(),
        'description'   => $purchasable->getDescription(),
        'purchasableId' => $purchasable->getPurchasableId(),
        'cpEditUrl'     => '#',
        'options'       => $this->options
    ];

    // Add our purchasable data to the snapshot, save our sales.
    $this->snapshot = array_merge($purchasable->getSnapShot(), $snapshot);

    $purchasable->populateLineItem($this);

    //raising onPopulate event
    $event = new Event($this, [
        'lineItem' => $this,
        'purchasable' => $this->purchasable
    ]);
    craft()->commerce_lineItems->onPopulateLineItem($event);
    // Always make sure salePrice is equal to the price and saleAmount
    $this->salePrice = CommerceCurrencyHelper::round($this->saleAmount + $this->price);
}

to
public function fillFromPurchasable(Purchasable $purchasable)
{
    $this->price = $purchasable->getPrice();
    $this->taxCategoryId = $purchasable->getTaxCategoryId();
    $this->shippingCategoryId = $purchasable->getShippingCategoryId();

    // Since sales cannot apply to non core purchasables yet, set to price at default
    $this->salePrice = $purchasable->getPrice();
    $this->saleAmount = 0;

    $snapshot = [
        'price'         => $purchasable->getPrice(),
        'sku'           => $purchasable->getSku(),
        'description'   => $purchasable->getDescription(),
        'purchasableId' => $purchasable->getPurchasableId(),
        'cpEditUrl'     => '#',
        'options'       => $this->options
    ];

    // Add our purchasable data to the snapshot, save our sales.
    $this->snapshot = array_merge($purchasable->getSnapShot(), $snapshot);

    $purchasable->populateLineItem($this);

    //raising onPopulate event
    $event = new Event($this, [
        'lineItem' => $this,
        'purchasable' => $this->purchasable
    ]);
    craft()->commerce_lineItems->onPopulateLineItem($event);
    // Always make sure salePrice is equal to the price and saleAmount
    $this->salePrice = $this->saleAmount + $this->price;// Modified: JL
}

Can anyone tell me if this is a known issue and whether there is any reason why the code is not like this to begin with? Why store values at 4dp if it's always rounded off to 2dp when calculated?

**Tags:** plugin-craftcommerce, rounding

---

## 1 answers

### Answer by username_1 on 2018-01-11. Score: 1 | Accepted answer

It is stored in 4 decimal places because some currencies have more than 2 decimal places.

We round before multiplication because we need don't store any value on the line item in a number of decimal places larger than the base currency. This allows us to add up line items without creating new cents.

As you suggest this could be changed to `CommerceCurrencyHelper::round($this->qty * $this->salePrice);` but this would be a breaking change for other users. We will consider it for Commerce 2.

I don't recommend using `> 2` decimal places to accomplish your "12 for 6.99" offer, rather make a discount adjuster that reduces the regular price of line item with a discount if `qty modulo 12 = 0`.

> **Comments:**
> **username_0**: Thanks for the response Luke. However as this is a production site with an order history, I'm not really a fan of changing all the pricing to fix this.  

---

