Quantity/unit price/total price auto-calculation rounding issue

Created on 25 March 2023, over 1 year ago
Updated 19 April 2023, over 1 year ago

Problem/Motivation

I discovered an issue with the logic for auto-populating price quantity value/unit price/total price in QuantityEventSubscriber that results in incorrect rounding.

The issue occurs because the Fraction class's multiply() and divide() methods automatically reduce the fraction after arithmetic is performed, which can lead to a fraction with a denominator that is not a power of 10.

For example, multiplying a value of 5 with a unit price of 5.25...

5/1 * 525/100 = 2625/100, which then gets reduced to 105/4.

When fractions with a denominator that is not a power of 10 are converted to a decimal with automatic precision (which happens when it is loaded into the Fraction widget and formatter), it tries to determine the precision, and falls back to a precision of the length of the denominator:

https://git.drupalcode.org/project/fraction/-/blob/306a04e91964bbbd03275...

In the case of 105/4, the denominator has a length of 1, so that becomes the precision used in rounding.

Thus, 105/4 equals 26.25, which gets rounded to a precision of 1, or 26.3.

Steps to reproduce

Create a Price quantity with a value of 5 and a unit price of 5.25. Save it and observe that the total price is calculated as 26.3.

Proposed resolution

TBD

Remaining tasks

TBD

User interface changes

None.

API changes

None.

Data model changes

None.

πŸ› Bug report
Status

Fixed

Version

2.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States m.stenta

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @m.stenta
  • πŸ‡ΊπŸ‡ΈUnited States m.stenta

    I think there are two potential ways to solve this:

    1. Do not reduce fractions when we use multiply() and divide() arithmetic.
    2. Improve the auto-precision logic so that it can determine if a fraction is terminating or repeating, and figure out the maximum precision for terminating fractions.

    Both of these would require changes in the upstream Fraction module. I opened a feature request to consider making reduce optional: ✨ Make fraction reduce optional after arithmetic operations Postponed

    I'm not sure how to determine the maximum precision of a terminating fraction... need to brush up on my maths. :-)

  • πŸ‡ΊπŸ‡ΈUnited States m.stenta

    Improve the auto-precision logic so that it can determine if a fraction is terminating or repeating, and figure out the maximum precision for terminating fractions.

    Opened another issue for that: ✨ Improve automatic maximum precision determination for terminating fractions Fixed

  • Status changed to Needs work over 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States m.stenta

    1. Do not reduce fractions when we use multiply() and divide() arithmetic.

    I confirmed that this does fix the issue, using the merge request I just opened in ✨ Make fraction reduce optional after arithmetic operations Postponed .

    The only thing I'm wondering is: does this have the potential to create fractions with numerators or denominators that are too large?

    ✨ Improve automatic maximum precision determination for terminating fractions Fixed feels like it would be the more elegant solution to this issue, if it's possible...

  • πŸ‡ΊπŸ‡ΈUnited States m.stenta

    Then again... maybe we should always try avoid saving denominators that are not factors of 10 in these fields, because they will always be factors of 10 when they get entered manually by the user (see Fraction::createFromDecimal()).

    The use of multiply() and divide() in this module's auto-calculation logic is the only time we are introducing fractions with a denominator that is not a factor of 10 (because of automatic reduce()).

    Since this module deals only with prices (in theory), and only shows them as decimals (in form and display), there is never a need to store fractions with a denominator that is not a factor of 10. So it would make sense that we try to enforce that within this module.

    The only thing I'm wondering is: does this have the potential to create fractions with numerators or denominators that are too large?

    Preventing the automatic reduce() ensures that the denominator will always be a factor of 10, but it means we could end up with unnecessarily large numbers like 1000000/10000000 (equivalent to 1/10).

    Maybe we need a reduce() method that keeps the denominator a factor of 10?

  • First commit to issue fork.
  • Status changed to Postponed over 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States m.stenta

    Update: ✨ Improve automatic maximum precision determination for terminating fractions Fixed will most likely be merged into Fraction soon, which fixes this issue.

    Since this module deals only with prices (in theory), and only shows them as decimals (in form and display), there is never a need to store fractions with a denominator that is not a factor of 10. So it would make sense that we try to enforce that within this module.

    Given that ✨ Improve automatic maximum precision determination for terminating fractions Fixed fixes the issue, I'm not sure we need to go any farther with this. We have to assume that there will already be data saved from this module, which means if we really wanted to enforce power-of-10 denominators.

    Perhaps the only thing we should do here is pin our version of Fraction to the version that includes ✨ Improve automatic maximum precision determination for terminating fractions Fixed

    Postponing this until that's ready...

  • πŸ‡ΊπŸ‡ΈUnited States m.stenta
  • πŸ‡ΊπŸ‡ΈUnited States m.stenta

    This was fixed upstream in Fraction 2.3.0. This module now requires that version as a minimum to fix this issue.

  • Status changed to Fixed over 1 year ago
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024