-
Notifications
You must be signed in to change notification settings - Fork 382
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
Suggestion: define conversions as rational values #478
Comments
Thanks for sharing! |
This is pretty cool. Today:
Proposed:
Breakdown
Size-wise, we should be good enough I think. Reference Let me know what you think and if I got anything wrong :-) |
Hi! Let me check on the weekend so I can elaborate on the idea. :D |
Hi, I've made a pretty simple example in this link: https://repl.it/@ArturoTorres/UnitsNET-rational-conversion Let me explain how I was thinking about it. The value themselves should still be stored in a simple value. I think for these calculations So, the proposed values would be: Proposed:
But then, the numerator and denominator will come into play in the conversions. First of all, for simplicity, I'm defining a
The reason these are This struct has multiplication and division defined, and also an explicit conversion to The next part, then, would be to define the conversions in a static dictionary defined for each class (or, with code generation, a switch would work as well)
And then the conversion would be applied as such:
This would make the calculations more precise while still storing a single value in each Unit. The Rational is just a helper for multiplications. What do you think? I hope it's not confusing. |
Great example, it clarified a lot for me, thanks! I now understood the part about only using the rationals for representing conversion factors. I'll get back to you on this, just wanted to chime in real quick. |
Great! Yes, using |
Well, if you want, you can start working on a PR for this. I see no downsides to this approach and we can always represent our current conversion factors as a numerator with |
I think I may have a better idea here. Working on a PR... |
Take a look at my idea in PR #588. I think rather than adding this complexity, it would be nice to have all unit conversions held in the UnitConverter class. This PR allows you to also add custom to/from conversions, including direct conversions from unit->target that avoid going from unit->base->target. Here's the code for going directly to US gallons from oil barrels: [Fact]
public void TryCustomConversionForOilBarrelsToUsGallons()
{
ConversionFunction conversionFunction = ( from ) => Volume.FromUsGallons( ((Volume)from).Value * 42 );
var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<Volume>( VolumeUnit.OilBarrel, VolumeUnit.UsGallon, conversionFunction );
var foundConversionFunction = unitConverter.GetConversionFunction<Volume>( VolumeUnit.OilBarrel, VolumeUnit.UsGallon );
var converted = foundConversionFunction( Volume.FromOilBarrels( 1 ) );
Assert.Equal( Volume.FromUsGallons( 42 ), converted );
} In this case, the answer is exactly 42. |
@tmilnthorp
|
The goals here were multifaceted: allow for higher precision conversion, allow conversion functions to be specified between different quantities, and to allow users to implement their own IQuantity and create their own conversions. I would not plan on inserting all of the permutations, just to/from base units. I need to add some code that checks if a conversion is explicit. If it doesn't exist, we can see if one exists from current units, to base units, to the target units. |
I don't think defining custom conversion functions is the way to go. It seems like a very easy way to get things wrong. My proposal was more about avoiding having exponentially many conversion pairs while still yielding an exact result. I'm thinking of another possibility... This all originates from conversions between SI and Imperial units. But, usually, Imperial units have whole numbers in their conversions (like, 1 yard = 3 feet, 1 foot = 12 inches, and so on). So, maybe, we could define to conversion "lines": a metric one and an imperial one, and a way to transition between the two of them. That way, when going from barrels to gallons, you can have an exact 42 multiplier, but when going to liters you can have the little approximation that comes with the division. |
I believe custom conversions are the only way to do it exactly. Specifying just a numerator/denominator is not enough. Take temperatures for example. It's a numerator/denominator plus an addition. Explicit SI/Imperial factors are also not always valid as some quantities are unitless! Today if you do this, you get 31.999999999999989 var temp = Temperature.FromDegreesCelsius( 0 );
var converted = temp.As( TemperatureUnit.DegreeFahrenheit ); However you can specify an explicit converter for C to F like so: ConversionFunction conversionFunction = ( from ) =>
{
var value = ((Temperature)from).Value * 1.8 + 32;
return Temperature.FromDegreesFahrenheit(value);
};
var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<Temperature>( TemperatureUnit.DegreeCelsius, TemperatureUnit.DegreeFahrenheit, conversionFunction ); Then this will give you exactly 32: var foundConversionFunction = unitConverter.GetConversionFunction<Temperature>( TemperatureUnit.DegreeCelsius, TemperatureUnit.DegreeFahrenheit );
var convertedByConverter = foundConversionFunction( temp ); Not that I would actually like to route the As/ToUnit methods through the unit converter so that explicitly defined units still work, so it wouldn't look any different. |
In this example it will be exactly 32, but, please keep in mind that floating precision types are never reliably exact to begin with when performing arithmetic:
I still need to mature the idea in my head a bit, but I think maybe there can be some advantage from both ideas here.
I agree with @R2D221 that there is added risk of getting direct conversions wrong so we need to test these conversions, which means quite a bit more manual work in adding and testing both new and old units. Also, the value of the slightly more precise conversions must be worth it. I personally am not very invested in increasing the precision, but if we can do it without adding a lot more effort, I'm all for it!
Does this improve the precision compared to what we already have with hard coded from/to conversion functions? It does sound elegant to simply do everything through one system though, which I think is what you are aiming for here. Performance Memory Converting between units of different quantities sounds interesting for #576. It would be nice if you passed in two units for different quantities, that were compatible through a direct conversion function, then it would all "just work". But, how should we communicate to consumers what units they can expect to convert between across quantities? Xmldoc on the unit enum values? Wiki-page? It's not very visible/discoverable and I also suspect this feature will not be used by many. Look forward to what you both think! |
Indeed! However it is more precise than the cumulative floating point error of unit -> base -> target unit! I think registering custom conversions would generally be done more frequently for external users of the library. For example, one that comes to mind is converting from 1 cubic centimeter to its weight in water (1g... on earth). As the library stands today, we don't support this, so an external user could then add it for convenience. If we found it was being used a lot, we could add a custom converter for this, and we'd probably add a ToWeightInWater() method on the Volume quantity directly. However keeping the converter is still handy for doing generic calculations where possible.
No, for most cases it would not. We would only add a custom conversion where necessary, an override of sorts. Performance wise, I think most of the hit is due to creating a Quantity rather than just a double. That said, I think that's important as you need to be able to see what you were converted to if it's a generic mechanism. |
Gotcha. What do you think about bullet point 1, is it still worth pursuing?
I believe it improves the precision, but it does come at the expense of a lot heavier quantities - roughly twice the size with numerator/denominator instead of value - if we use doubles instead of decimal as suggested. I don't know yet what I prefer. As I've eluded to before, precision has not been a concern for my uses of the library and this is really the first time anyone ever asks about it for the 5 years this library has existed. I think I need some hard numbers on how much better precision we would get in some concrete examples and see that in light of how many more bytes we need to allocate per quantity, in order to be able to consider it further. @R2D221 could you draft up a comparison or some kind of benchmark that illustrates the gain in precision vs bytes allocated? As for direct conversions, I think that sounds like a good idea regardless:
Let's move the conversion system discussion to #588 . |
@R2D221 Are you still interested in pursuing the rational value proposal? As mentioned above, we need some benchmarks or some kind of metric to compare before/after in terms of precision vs memory use and performance. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
I recently tried to do a simple conversion between US gallons and oil barrels. The conversion is simple, you just need to multiply or divide by 42. However, with this library, the result is the following:
I have already read the Precision page, so I understand how the conversions are made and how it's not the first priority. However, I want to post this as a suggestion.
Instead of defining the units as a double, they could be defined as rational.
How would this work: Each conversion should have the numerator and denominator. This leaves things almost unchanged for e.g. meters to kilometers. But for conversions between metric and imperial systems, it would look as follows:
So, by storing
473176473, 125000000000
and9936705933, 62500000000
as the conversion values for gallons and barrels, you can then convert between them like so:If you input an integer, it will give you an exact multiple of 42.
Of course, this isn't exclusive to gallons, but pretty much any conversion between imperial and metric systems would benefit from this, especially since most if not all of these units are defined in metric terms anyway (like an inch being exactly 0.0254 meters).
I know maybe this isn't in your plans, but I think the system could internally be adapted to make the calculations like this while improving precision. On the other hand, I don't know the performance impact of this, but I don't think it would be that much of a difference.
The text was updated successfully, but these errors were encountered: