Fractions icon indicating copy to clipboard operation
Fractions copied to clipboard

Operations with unreduced fractions (WIP)

Open lipchev opened this issue 9 months ago • 0 comments

Following the discussion in #49 here is a POC with special handling for the operations of the non-reduced fractions.

Motivations:

  1. Given that all existing operations return a normalized fraction- the current code-base doesn't offer much use for the concept of a non-normalized fractions (other than the initial construction speed).
  2. There aren't many ways of reducing a decimal number (unless it has trailing zeros) - with the result having ~ the same size.
  3. Operations with BigInteger that have a trivial value (smaller than int.MaxValue) perform the same way, in which case using the GDC reduction on every operation becomes a lot more expensive.
  4. When fractions are constructed using similarly-formatted numbers (e.g. with 2 decimal places) without normalizing the denominator, subsequent operations such as Add/Subtract are much faster
  5. Unless constrained, the Multiply and Divide operations would eventually grow beyond the trivial size and become slow to work with.

I've tried to address most of these using the following strategy (breaking change):

Assuming:

NF = Non-normalized (improper) fraction
F = normalized fraction
⊙ = mathematical operation having two operants (+, -, *, /, %)
F ⊙ F = F
NF ⊙ F = NF
F ⊙ NF = NF
NF ⊙ NF = NF

I initially tried to address 5) by replicating the rules that are employed by the decimal type (these are the only docs I was able to find on the subject). However that didn't satisfy the constraints that I had set for myself:

  1. x * 1000 / 1000 == x should be true
  2. x / 1000 * 1000 == x should be true
  3. x * 2 * 5 should be the same as x * 10

Note, that this isn't a strict requirement for 5) but, if possible, I wanted to be able to express the following concept: 1.00000 g * 1000 = 1000.0 mg and returning back to 1.00000 when dividing by 1000 (as if the user is switching the balance display from grams to milligrams).

The reduction strategy for the multiplication is to:

            ReduceTerms(ref thisNumerator, ref otherDenominator); 
            ReduceTerms(ref otherNumerator, ref thisDenominator);
            return new Fraction(true,
                thisNumerator * otherNumerator,
                thisDenominator * otherDenominator);

While the Division is just expanding:

            return new Fraction(true,
                thisNumerator * otherDenominator,
                thisDenominator * otherNumerator);

Despite my best efforts, I wasn't able to balance the operations- everything i tried ended up failing one of 1) or 2) or 3) - if you can spot a possible reduction scheme that works, that would be great. Otherwise, I think we can live with the slight bias - the only degenerate case would be for the user to keep dividing a number by fraction that is smaller than 1 (which can be optimized by multiplying by the reciprocal)

  • [x] Operations with non-reduced fractions should now produce non-reduced fractions (Add/Multiply/Divide) while preserving the number precision (denominator)
  • [x] Added an optional reduceTerms parameter for (most) operations.
  • [ ] Add parameter to the FromString overloads
  • [ ] Possibly include a special string format that preserves the trailing zeros

More details coming soon..

lipchev avatar May 12 '24 14:05 lipchev