Operations with unreduced fractions (WIP)
Following the discussion in #49 here is a POC with special handling for the operations of the non-reduced fractions.
Motivations:
- 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).
- There aren't many ways of reducing a decimal number (unless it has trailing zeros) - with the result having ~ the same size.
- Operations with
BigIntegerthat have a trivial value (smaller thanint.MaxValue) perform the same way, in which case using the GDC reduction on every operation becomes a lot more expensive. - 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
- Unless constrained, the
MultiplyandDivideoperations 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:
-
x * 1000 / 1000 == xshould be true -
x / 1000 * 1000 == xshould be true -
x * 2 * 5should be the same asx * 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
reduceTermsparameter for (most) operations. - [ ] Add parameter to the
FromStringoverloads - [ ] Possibly include a special string format that preserves the trailing zeros
More details coming soon..