Skip to content

Conversation

lipchev
Copy link
Collaborator

@lipchev lipchev commented Apr 13, 2025

  • QuantityValue implemented as a fractional number
  • IQuantity interfaces optimized (some methods refactored as extensions)
  • UnitInfo: introduced two new properties: ConversionFromBase and ConversionToBase which are used instead of the switch(Unit) conversion
  • UnitsNetSetup: introduced helper methods for adding external quantities, or re-configuring one or more of the existing ones
  • UntAbbreviationsCache: introduced additional factory methods (using a configuration delegate)
  • UnitParser: introduced additional factory methods (using a configuration delegate)
  • UnitConverter: re-implemented (multiple versions)
  • Inverse relationship mapping implemented as a type of implicit conversion
  • updated the JsonNet converters
  • introducing the SystemTextJson project
  • added a new UnitsNetConfiguration project to the Samples, showcasing the new configuration options
  • many more tests and benchmarks (perhaps too many)

- IQuantity interfaces optimized (some methods refactored as extensions)
- QuantityInfo/UnitInfo hierachy re-implemented (same properties, different constructors)
- QuantityInfoLookup is now public
- UntAbbreviationsCache, UnitParser, QuantityParser optimized
- UnitConverter: re-implemented (multiple versions)
- removed the IConvertible interface
- updated the JsonNet converters
- introducing the SystemTextJson project
- added a new UnitsNetConfiguration to the Samples project showcasing the new configuration options
- many more tests and benchmarks (perhaps too many)
@lipchev
Copy link
Collaborator Author

lipchev commented Apr 13, 2025

@angularsen Clearly, I don't expect this to get merged in the Gitty up! fashion, but at least we have the whole picture, with sources that I can reference.

If you want, send me an e-mail, we could do a quick walk-through / discussion.

lipchev added 2 commits April 18, 2025 00:27
…lection constructors with IEnumerable

- `UnitAbbreviationsCacheInitializationBenchmarks`: replaced some obsolete usages
@angularsen
Copy link
Owner

100k lines removed, 100k lines added 🙈

image

@lipchev
Copy link
Collaborator Author

lipchev commented Apr 18, 2025

100k lines removed, 100k lines added 🙈

I tried to create this PR twice before (many months ago), while the changes to the unit definitions were still not merged- and the web interface was giving me an error when trying to browse the files changed.. Something like "Too many files to display" 😄

@angularsen
Copy link
Owner

Ok, I'm not going to get through a review of this many files anytime soon.
Maybe we should have a screen sharing session and go through it together. I may have some time this weekend, what about you? What timezone are you in?

On the surface though, it seems like this could be split up into chunks. I know it's tedious and extra work, but it will be way faster to review. Do you see any chunks of changes to easily split off into separate PRs?

@lipchev
Copy link
Collaborator Author

lipchev commented Apr 18, 2025

Ok, I'm not going to get through a review of this many files anytime soon. Maybe we should have a screen sharing session and go through it together. I may have some time this weekend, what about you? What timezone are you in?

Sofia (GMT+3), but time zones are not relevant to my sleep schedule - so basically any time you want.

On the surface though, it seems like this could be split up into chunks. I know it's tedious and extra work, but it will be way faster to review. Do you see any chunks of changes to easily split off into separate PRs?

Yes, I do have some ideas:

  1. UnitAbbreviationsCache and the UnitParser should be more or less free of changes once UnitAbbreviationsCache.CreateEmpty should use the default QuantityInfoLookup #1548 is merged
  2. I plan to remove the IConvertible interface tonight (lots of red points there)
  3. The QuantityParser has just a few minor changes which I was going to try to push as well (other than that it's mostly just double changing to QuantityValue)
  4. QuantityFormatter - there was an issue that I created earlier that should (mostly) solve the differences
  5. Refactoring the QuantityInfo can theoretically be done without the ConversionExpressions (which would open the way for the changes to the IQuantity interface and some of the extension methods).
  6. UnitParser: introduce two new method: GetUnitFromAbbreviation and TryGetUnitFromAbbreviation returning a UnitInfo (which could be used for constructing an instance of the quantity)
  7. Introduce the changes to the IQuantity interface and some of the extension methods
  8. Move the exceptions in their own folder and replace the usages of the NotImplementedException with the appropriate UnitNotFoundException
  9. Update the UnitTestBaseClassGenerator - I've refactored the Parse/TryParse tests (completing the test coverage) - having a look at the diff on the MassTestsBase.g.cs, it looks like these account for about half of all diffs in the PR 😄
  10. Make the QuantityInfoLookup public: apart from the extra constructors, there doesn't appear to be any other differences- and I don't see any reason to keep it internal
  11. Introduce the CodeGen/Helpers/ExpressionEvaluationHelpers.cs + CodeGen/Helpers/ExpressionAnalyzer and replace the unit conversion expressions such as (_value / 1e3) * 1e-2d) with the simplified expression (unless we actually plan to use the the rest of this PR- this would probably be an overkill).
  12. The JsonQuantityConverter stuff from SystemText could theoretically come with it's double versions first (but we do need to have a discussion about it)

Hopefully by the time we get to 5) you'd be up to speed (and fed up with PRs) and we can turn back to reviewing / working on the rest of it as a whole 😄

@angularsen
Copy link
Owner

Ok, sounds good. Just send PRs my way and I'll try to get to them. I have a little bit of extra time this weekend.

Copy link
Contributor

This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.

Copy link
Contributor

This PR was automatically closed due to inactivity.

@github-actions github-actions bot closed this Jun 28, 2025
@lipchev
Copy link
Collaborator Author

lipchev commented Jul 30, 2025

@angularsen FYI - I'm keeping the nugget version in this PR one step ahead of the official pre-release version so that the nugget references in the Samples projects pick up the right one (same goes for my nugget sources).

PS Before running the Samples, make sure you have built the Artifacts (from the global build script).

lipchev added 24 commits August 3, 2025 09:15
- replaced the type of the tolerance parameter of the Equals extension with the concrete quantity type
…l in net8+

- cleaning up some of the old benchmarks
@lipchev
Copy link
Collaborator Author

lipchev commented Sep 22, 2025

@angularsen I've synced the changes from upstream, however instead of bringing down the diffs, this has added another ~200 file changes (the new NumberExtensions).

I've updated the task list (completing the IQuantity interface refactoring). The next task on the list is

Introduce the CodeGen/Helpers/ExpressionEvaluationHelpers.cs + CodeGen/Helpers/ExpressionAnalyzer and replace the unit conversion expressions such as (_value / 1e3) * 1e-2d) with the simplified expression (unless we actually plan to use the the rest of this PR- this would probably be an overkill).

... but that can only start after I've had a confirmation, that we're moving towards replacing the double (in v6).

@angularsen
Copy link
Owner

@lipchev I really like the concept of the decimal fractions, it solves equality issues and precision, and if I recall correctly it remains fairly backwards compatible.

I want to say let's go, but if you could please help me out and give a short summary for a couple of things that would help a lot as I don't recall and I'm pressed for time.

  • How does it affect performance? CPU time on creating a quantity, converting units and ToString are perhaps the big ones. Allocations are also relevant. I don't need full benchmarks, just a top level understanding of are we talking 2x, 10x, 100x orders of magnitude.
  • Any significant breaking changes to be aware of?

@lipchev
Copy link
Collaborator Author

lipchev commented Sep 22, 2025

* How does it affect performance? CPU time on creating a quantity, converting units and ToString are perhaps the big ones. Allocations are also relevant. I don't need full benchmarks, just a top level understanding of are we talking 2x, 10x, 100x orders of magnitude.

I spent a full Sunday back in February creating a comparison chart, but before I could finish up setting up all the sheets, I accidentally executed the build script which erased all of my benchmark results. 😠 I'll attach the file as it is, but the gist is that compared to v5 there should be a significant performance improvement, especially when it comes to the strings.
UnitsNet-Benchmarks.xlsx

The conversions are about the same, or faster (with units other than the BaseUnit). There are no allocations, as long as the Value is represented by a fraction, who's terms are smaller than int.MaxValue.

For example the value 42.24 is represented as 4224 / 100, which when multiplied by 1000 (some Kilo conversion) would become 4224000 / 100 which is still small enough. Note that I intentionally avoid the (possible) reduction, since keeping the original denominator generally makes the additions / subtractions more efficient: for example adding 1.23 to would result in 4224123 / 100 (simply adding the integers in the numerators). So, while dividing 4224000 / 100 by 1000 would revert back to 4224 / 100, doing a bunch of multiplications (without manually calling QuantityValue.Reduce) would sooner or later hit the magical threshold (int.MaxValue) which would trigger the allocation of the uint[]? _bits. From there on, the performance scales linearly with the size of the bits increasing by 1 for every power of int.MaxValue (my PC was still able to do square roots, even after something like 500K digits of precision). Anyway, I've got a ton of benchmarks of the implementation over at the Fractions repo (with loads of pretty charts) - when we factor in the occasional allocations, the raw arithmetic-performance was within the same order of magnitude as the decimal (it was 2x-6x slower back on Jul 1, 2024, and I expect the QuantityValue to be slightly faster).

The initialization is slower but no more than 10x, judging from these benchmarks but that can be reduced by loading a select subset of quantities (I imagine loading just 10 QuantityInfos would still at least 2x faster).

PS The Inverse method is also something like 2x slower (but that's due to the extra logic)...

PS2 Aside for the static benchmarks, I've also observed a visible improvement (in the performance logs) after migrating my code-base, but that's probably due to the improvements to the string handling stuff around the UnitParser, UnitAbbreviations etc. (from v5 to v6).

* Any significant breaking changes to be aware of?

Compared to the v5 the only significant change is that

  • double value = Mass.Zero.Value is not allowed
  • decimal value = Power.Zero.Value is not allowed
    both of these return a QuantityValue which does not support the implicit conversion to double or decimal:
  • double value = (double)Mass.Zero.Value is allowed
  • decimal value = Power.Zero.Value.ToDecimal() is also allowed

While inconvenient, this is the correct way to do it (implicit conversions from, and explicit conversion to any primitive number).

By the way, given that in v5 IQuantity.Value is still defined as QuantityValue (without an explicit cast as far as I remember), this shouldn't be that much of surprise 🤷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants