-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Discussion] Use cases for the FractionState #49
Comments
The enum was originally intended (as a bit field) to signal states such as IsNormalized, NaN and Infinity.
Since I didn't see any use case for NaN & Infinity at the time, it stuck with I don't see any advantage in introducing two data types (one for normalized, one for non-normalized fractions). In fact, by default, users automatically generate normalized fractions. All subsequent operations on these fractions are automatically normalized (if not, it is a bug 🥲). The Fraction data type is a The only exception to this rule are negation and the reciprocal. The expectation as a user would simply have been to multiply by -1 or swap the numerator and denominator. I realize that this doesn't seem very consistent. |
I'll close this issue - further discussions can be done in #26 |
Upon further reflection, I realized that this may not be always the case. Consider this scenario:
Normalized fractions would each have their own denominator - adding them together would require calculating the GCD and doing the extra operations on the 4 BigIntegers, for each consecutive addition- with a good chance of getting the same Numerator/Denominator in the end. |
I would say it depends very much on the calculations that are done. I can only speak from our experiences here: In general, the product performed better when we kept the numerators and denominators as small as possible. We have had good experiences with normalized fractions, particularly in serialization scenarios (saving to a DB) and conversion to decimal (for display in the user interface). This of course applies to version <= 6.x.x. |
Yes, the multiplications are definitely going to be the least performant operations in the case of the non-normalized fractions (even the divisions are probably going to be faster). However, if we follow the division rule as well, then that would actually bring the size of the |
Before I move on I just wanted to highlight this exception to the rule (works for both a+0 and 0+a):
As for the performance benefit- I did a POC (without splitting the types) and the benchmarks indicate that as long as there is a rule in place that constrains the unlimited expansion of the BigIntegers, the results are much better without the normalization. Comparing the results using the |
Yes, I imagine there are many use cases where constant normalization degrades performance. We had a specific problem back then: Drugs that are dosed in nanograms as a running rate on a syringe pump (e.g. 5 ng/kg/min) were problematic. When the application converted between volume flow rate (ml/h) or active ingredient dosage, the result was often an incredibly large fraction. Calculations with such large numbers were significantly slower (at least ~10 years ago) - and users noticed this as soon as the UI interface performed further calculations based on them. But time goes on. I no longer work on the medication module and probably none of my colleagues have given such considerations to performance in recent years - once it's running, you don't question it anymore. |
Theoretically, you don't need two data types. When creating a Assuming:
We could introduce the following (breaking) change:
There is a |
Excellent, that's exactly how I modeled it in the POC. Right, those are the exact use cases I'm considering (and trying to optimize for)- in UnitsNet currently when doing any type of math operations with quantities such as Mass/Volume/MassConcentration etc. often require an intermediate conversion to a base unit and back (e.g. "1mg + 1ng" would have to go via the (SI) base unit "g") - so for most quantities (except for the Freedom Units) we are constantly doing multiplications and divisions by a factor of 10. I'll just merge my branches and show you what i got |
Thanks. However, I must apologize in advance. I'm currently busy with other topics both professionally and privately and travel a lot - so I won't always be able to respond promptly. I assume we'll have to wait a bit until 8.0.0 is released. The above would be a big (but meaningful) breaking change |
Yes, I don't think we'll manage this weekend 😄 . I'll also be absent for most of the week, but will try to iron out most of what's left until I leave on Tuesday. |
Given that #65 is now merged, I think the only thing missing from this feature is a factory method that exposes the private constructor call:
.. is what I was thinking, but not sure where the word I've already mentioned the performance improvements that I worked over the last weekend (I think it was), but I haven't had time to clean up and test it against the As for advertising the trailing zero and the decimal shifting logic- I'd personally hold off on that, In the benchmarks section I've only mentioned the performance aspects. Anyway, as long as we don't specify
|
Tried it, it didn't work.
Tried a single method for Multiply / Divide that does some reduction (using the I think it's better if we removed the reduction-stuff altogether. I don't like that it's asymmetric, the implementation of |
Lol, I can't believe I didn't realize this earlier- of course they should be asymmetric, just not the way we have them setup ATM:
Of course, there are the benchmarks testing the multiplication of numbers such as |
I think this discussion is resolved with pull request #72. I'll close the thread. |
I'm curious about the use cases you have for the
FractionState
and weather you intend to extend or reduce the support forImproper
(i.e. non-reduced/non-normalized) Fractions moving forward.Here are some observations we can make:
Improper
option in theFractionState
Improper
state is required, the field could be replaced by a the equivalentprivate readonly bool _hasBeenNormalized
(which, if necessary, could still be used to return the original State property)Multiply
andDivide
currently result in aFraction
with anIsNormalized
state (regardless of the input state(s))Normalized
fractions can be thought of as using an upgraded version of thedouble
type where as one that operates withImproper
fractions is (IMO) similar to operating on an upgraded version of thedecimal
typeNormalized
fractions are generally faster than the equivalent operations onImproper
fractionsFraction
,NormalizedFraction
) using separate types having an implicit conversion between one another (without the need for any_state
fields)Here is an example of what I mean by having decimal-like operators:
The text was updated successfully, but these errors were encountered: