-
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
Support for NaN and Infinity #26
Comments
Thanks for this suggestion. Perhaps I should say something about the history of this project. Fractions started as a private PoC for calculating drug dosage for clinical applications. The software of my employer at the time had major problems in displaying calculated volume rates, active ingredient rates and summations of critical active ingredient quantities over a specific period of time. (Sorry for the German language in the pictures - I just paste parts of my old slides ;-) The data type double has too large rounding errors and is therefore disqualified for drug dosages prescribed in the nanogram range. The data type decimal is less susceptible to the typical errors of floating-point arithmetic due to the base of 10 - BUT - periodic decimals (which could occur briefly during computation) continued to be a problem. So, the rounding errors were still an issue when using the decimal data type. And the doctors and nurses freaked out... Instead of an infusion rate of 5mg/kg/h, the UI said 4.997mg/kg/h OR even worse 0.998 pill instead of simply 1 pill. That's how the Fraction data type came into play. Together with a unit conversion system (a project that I am unfortunately not allowed to publish as open source), we were able to display exact calculations in the blink of an eye. Performance was a key feature. In the weekly or monthly view, 100 to several 1000 calculations per second are performed. The data types double and decimal were fast enough here - Fraction had to be able to keep up. Delays in UI rendering were not accepted by medical staff. So all the code has been optimized for the highest possible execution speed. We thought about whether we also wanted to represent complex numbers, infinity, etc. However, we simply couldn't think of any use cases for this in our "real world" application. That's why it is not available (yet). However, I don't have any problems if the data type is improved by the new features. Point 1 of your proposal poses a problem: Since I don't have a PhD in mathematics - so I can only make unvalidated assumptions here. I don't know if it makes strategic sense to represent infinity and/or NaN in the numerator or denominator. I would probably drill the 'FractionState' enum as a bit field and put the additional states there. We could stay compatible with serialized data, because |
Right, those are exactly the same concerns we have in UnitsNet which has historically struggled with the tradeoff between the double type and it's binary representation vs the decimal and its limited range. I'm sure you can see how it all fits together seeing this one line of code (which is now evaluating as expected): As for the NaN / Infinity use cases- there are (IMO) some valid reasons for having them in UnitsNet, however- given that this is arguably a breaking change for the clients of this library, you should consider whether you think it's worth having it or not. Regarding the performance, I'm pretty sure it wouldn't be any slower- in any case, we should be able to validate that using the baseline benchmarks from #29 . In the meanwhile, there are a few extra features that I could offer- I'll create separate PRs for each of them, feel free to reject any you think you don't need. |
Thank you very much for the pull requests. I've already merged most of these into the master branch. Maybe you want to take a look before I release version 8.0.0 - I've made a few changes. You've added a few TODOs regarding string allocations. I "doubled" these methods and switched from String to ReadOnlySpan. I've also renamed the Round methods, which returned a BigInteger instead of Fraction. |
Regarding NaN/Infinity - the breaking change for the current users is manageable. In order to remain SemVer compatible, I would then increase the major number again and we would have to explicitly point this out in the changelog. |
This all looks to me. As for the release, it's up to you- I am currently preparing another pull request for a
Here is the current implementation And here are the tests: This is obviously an optional feature, so shouldn't matter for most people- so it's up to you. Additionally, I was thinking of adding the And another "extra-extra" feature (that would probably be suitable for an extension nuget) is a an expression parser that I wrote: In short it is parsing and expanding strings containing expressions like "123e-4 * (x*123.4) + 42", similar to what this does: I haven't written any tests for it yet, but have successfully parsed all the expressions from our json files: |
I like the approach of the Regarding the DataContract/DataMember attributes, what are they for? Fraction is a struct type and therefore cannot be deserialized easily. Maybe there are new possibilities with Expression parsing is the task of a CAS (Computer Algebra System). I think Fraction should remain as a simple data type (just like long, int or decimal). Of course, with the necessary features to be able to use it in a variety of ways. Since 8.0.0 is allowed to contain breaking changes - what features do you require? (counting your pull-requests you seem to have a lot in mind with it) Unfortunately, we can't easily change the behavior of the I'd wait for your 'GO' before releasing a new NuGet package. |
Nah, I don't expect a change of the default ToString() behavior- this is meant as an option- in our case our Quantities (e.g. Mass / Volume etc) have their own ToString() implementation which formats the Fraction using the DecimalFormatter (as we have to keep it backwards-compatible). This is the use case I foresee for other people as well. The I've got another small PR with some performance improvements on the FromDouble method that I'd like to push before I get on with the NaN stuff. I'll create the PR in the next hour or so.. |
As for the release version, it's up to you, but given that we haven't introduced any breaking changes yet, I'd probably release it as the "latest stable v7" so that people who want to opt-out of the NaN stuff can stick with v7.5 (or whatever the number is) and still benefit from the good stuff we're bringing in... |
I do not think we need any more improvements in the v7.x branch. I've merged the NaN pull-request into the master. |
The master-branch is up-to-date with your latest pull-requests. I'll wait with 8.x.x NuGet packages until we agree that master is stable. |
To pick up our old conversation in #51. I just see that it is not easily possible to remove Specifically, the two constructors are in conflict: private Fraction(BigInteger numerator, BigInteger denominator, FractionState state) and public Fraction(BigInteger numerator, BigInteger denominator, bool normalize) |
Yes, i had to switch the order (will post the PR in a bit):
|
I am working on another proposal right now. |
OK, then I can discard my branch - the changes correspond to mine. |
Are there any changes that need to be implemented for the 8.0.0 version, or can I update the documentation and prepare the release? |
I haven't got anything prepared, but some house cleaning might not be a bad idea- a solution reformat, and re-comment (I think I saw somewhere that "..NaN is not supported"). |
Regarding the clean-up work - I'll take a look at it later this week. |
@lipchev |
I think there are just some parts of the main Readme.md that could be improved: there is currently no mention of |
Lol, I was just finishing up the Readme.md section about "Working with |
@lipchev |
Support for NaN and Infinity
Hey,
I've been digging into the code and I think we can add support for NaN and +/- Infinity. Here's what I'm thinking:
GetReducedFraction(..)
to return 0/0 (NaN) for 0 in the numerator and denominator. Right now, it's returningZero
.We might need to tweak
FractionState
to{Zero, Unknown, Normalized}
to avoiddefault(Fraction)
being NaN. Also, in that case,Reduce()
should check forFractionState.Zero
and return a trueZero
(0/1).There might be a few more checks needed (like in the constructor from
double
), but I think it's doable.I'm ready to make these changes and submit a PR if you're on board with this.
Let me know what you think!
The text was updated successfully, but these errors were encountered: