-
Notifications
You must be signed in to change notification settings - Fork 393
Design proposal: Base type (WIP) #371
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
Comments
Great proposal! Just one thing I couldn't understand. Why do we need Also, would it be better if we prepare a UML class diagram for this discussion? I think it creates less confusion than a text proposal. |
A
So, I can't immediately think of any good scenario where |
### Motivation Preserve the original value and unit and add getter properties `Value` and `Unit` to expose them. Use `Unit` in `ToString()` instead of the base unit representation, to better match the units the user is working with. This is a significant rewrite of how values are stored and how conversions are performed in quantity types. It is also the first baby steps of introducing some common values in base types #371 . **There should be no breaking changes in neither main lib nor JSON serialization.** ### Changes Using example of `Length`, but this applies to all quantities: - Change private field `_meters` to `_value` (still `double` or `decimal`, as before) - Add private field `LengthUnit? _unit` - Add `Value` getter prop (`double` or `decimal`) - Add `LengthUnit Unit` getter prop, with fallback to base unit `LengthUnit.Meter` (1) - Ctors without unit param assumes `BaseUnit`, backwards compatible - Change `ToString()` to use `Unit` instead of `DefaultToStringUnit`, new test cases - Mark `DefaultToStringUnit` as obsolete - Add support for preserving `decimal` precision in `QuantityValue` (2) - Defer conversion to base unit until calling properties like `.Centimeters` (3) - Serialize `Value`+`Unit` instead of base unit and base unit value, backwards compatible, update test cases - Make internals of UnitsNet visible to Json to reuse reflection abstraction code - Rename variables in JSON serialization lib (use "quantity" term instead of "unit") 1) Default ctor (`struct`) and ctors without unit param assumes base unit, to be backwards compatible. We might want to change this in the future to require explicitly specifying the unit. 2) Needed to avoid precision issues going via double to construct decimal values. 3) Getting the value of unit properties is now twice as slow, as it first converts from `Unit` to base unit, then to target unit. Before this change, the first conversion was initially made when constructing the quantity. However, there are optimizations in-place to avoid converting if `Unit` already matches either base unit or the target unit. ### Gotchas - Serialization output changed (different value and unit for same quantity) - In #389 (comment) I discuss the choice to not go with breaking change with regards to unspecified unit, but why we may want to do so later to require explicitly specifying the unit
What is the state of this? I saw in the v4 prerelease there is an I am developing an desktop app for automatic measurements (Calibrations, etc.) and was trying to implement this library looking for sth. like |
@rgomez90, what issues are you seeing? IQuantity is in master, however there are only two properties on it currently. Unfortunately all other methods need a unit type enum that is specific to each quantity type. |
Unfortunately you can't do something like this in C# at all. You can't impose operator constraints on generic types. The best we could do is prove Add/Subtract/Multiply/Divide methods on IQuantity that takes another IQuantity (and optionally the desired result unit, otherwise default to BaseUnit). A quantity mismatch would be a runtime exception in that case, not enforceable by compiler constraints. |
That would be nice. Also to be able to create new Quantity like I tried to implement this adding following
public interface IQuantity<T> : IQuantity where T : IQuantity<T>
{
T Add(IQuantity<T> quantity);
T Substract(IQuantity<T> quantity);
T Multiply(double scalar);
T Divide(double scalar);
}
public Pressure Add(IQuantity<Pressure> quantity)
{
Pressure pressureStruct = (Pressure)quantity;
return new Pressure(this.AsBaseUnit() + pressureStruct.AsBaseUnit(),Pressure.BaseUnit);
}
public Pressure Substract(IQuantity<Pressure> quantity)
{
Pressure pressureStruct = (Pressure)quantity;
return new Pressure(this.AsBaseUnit() - pressureStruct.AsBaseUnit(), Pressure.BaseUnit);
}
public Pressure Multiply(double scalar)
{
return new Pressure(this.AsBaseUnit() * scalar, Pressure.BaseUnit);
}
public Pressure Divide(double scalar)
{
if (scalar <= 0) throw new ArgumentException("Only positive values allowed", nameof(scalar));
return new Pressure(this.AsBaseUnit() / scalar, Pressure.BaseUnit);
} Could be this a possible implementation? But this way every time boxing will happen or I am wrong? I also run a few unit tests and all passed without errors. See I don't use a quantity when multiplying or dividing, since that would have a different "meaning" (and also forces units change). Still wondering now, how to get the value through I am starting to try to contribute to some projects (since I don't have lot of experience) and just want to give it a try here.
|
The implementation looks about right. Agree on using scalars for multiplication/division. BaseDimensions are the SI unit dimensions of a quantity. All quantities in the world are derived from the 7 SI base units and we intend to use this to generate operator overloads in the future, since you can use it to determine that I'll let @tmilnthorp chime in on your draft first, but I'm positive about a PR on this change. Please target your changes on top of |
IQuantity will not work for WRC |
FYI the reason you can't get the value from IQuantity is because they are different types for different quantities. Some are double, some are decimal. IQuantity would permit this, however WRC again would not work. Not sure how far we cant to leave it behind considering operator overloading already does not work. |
@tmilnthorp I am more and more convinced we need to generate a separate source code for WRC (Windows Runtime Component, for everyone else's reference), the same way I think it would be awesome to generate TypeScript/Flow/JavaScript using the same unit definitions and generator scripts as a base. It's just too much compatibility issues to keep track of and other languages will bring new capabilities or new constraints. A digression, but... To avoid the huge diffs for every PR, which is seriously becoming a problem, we should also consider these options:
|
Regarding For ex. in my app I need to calculate the nominal signal {ns} of a transmitter given an input {i}: ns = 4 + ((i - RangeMin) / RangeDelta) * 16 with 4.Milliamperes() +
input.Substract(Specimen.MeasuringRange.Minimum)
.Divide(Specimen.MeasuringRange.Delta) * 16.Milliamperes(); and return the nominal signal as for ex. A possible implementation could be public double Divide(IQuantity<T> quantity)
{
return this / (T) quantity;
} I saw for ex. public double Divide(IQuantity<Temperature> quantity)
{
return this.AsBaseUnit() / ((Temperature) quantity).AsBaseUnit();
} |
Because 0 Kelvins != 0 Celsius. So you construct Temperature T1 = 10 Celsius. I believe we support So back to your proposal, yes, a numeric ratio makes sense for most quantities that share the same zero point, but Long story short, I don't think we can provide division generically the way I currently understand it. |
I think largely this issue is solved by |
NOTE: This is work in progress and early thoughts, but input is most welcome.
An interface or abstract type implemented by all quantity types, such as
Length
andMass
.Motivation
Length
orMass
but want to communicate it is a quantity - we need to useobject
todaysomeQuantity.ToString()
=>"5 kg"
, this works today except we need to treat it asobject
and we don't have access to overloads for things like culture and significant digits after radixDiscussion points
UnitSystem
type. One thing to consider is what culture to use for things like abbreviations and number formatting, whereUnitSystem
instances are created explicitly with a given culture (default uses CurrentUICulture in Windows).Proposal
IQuantity
since structs can't inherit base types, but users should be wary that casting to interface means boxing the value with those performance implicationsQuantityType
since quantities already expose this valueUnitSystem.Default.GetAllAbbreviations()
"ForcePerLength"
and"Force per length"
forms, to make it easier to textually describe the quantity type. Currently you use reflection to obtainQuantityType
enum value and callToString()
on it to get"ForcePerLength"
.QuantityType
as this currently requires reflection (see sample app https://github.com/angularsen/UnitsNet/pull/380/files#diff-6f7804b386e2c6cd78fb5a331e9c9a58R18)-UnitConverter.ConvertBy methods should not use reflection as this is unnecessary brittle and can be solved by generating code to check if from/to unit types are LengthUnit and passing that to
Length.From()
andLength.As()
methodsParse()
andToString()
, so not sure how much more to gain here.Case studies
Converter app with hard coded quantities and using strings for conversions
https://github.com/angularsen/UnitsNet#example-creating-a-unit-converter-app
UnitConverter
to convert between two units of a quantity like this:Converter app with generic quantities #353
The main difference to the example above is that we want to obtain the list of units for any selected quantity, without knowing the quantity type.
We either have the quantity string (
"Length"
or"Mass"
), or its equivalentQuantityType.Length
andQuantityType.Mass
enum values, but there is currently no intuitive way of getting abbreviations from this.Challenge: Present the plural name of any unit
For listing units in the GUI. We can get "meter" and
"newton per meter"
fromLengthUnit.Meter
andForcePerLengthUnit.NewtonPerMeter
by callingToString()
and splitting the pascal case into lower case words, but it would only be singular and difficult to reliably present in plural form. The plural form is defined in JSON, so we can easily generate that.Challenge: Present abbreviation of any unit
This currently requires you to know the quantity or unit type, in order to look it up statically via
string x = Length.GetAbbreviation(LengthUnit unit)
or dynamically viastring x = UnitSystem.Default.GetDefaultAbbreviation(Type unitType, int unitValue)
This dotnetfiddle shows how to get all length abbreviations, given that we added
LengthUnit
values to the list and can resolve abbreviations based on that type:https://dotnetfiddle.net/9f5TiF
A base type could have the method `GetAbbreviation(object unit)
Discussion on converting from
struct
toclass
to supportdecimal
#285This is related, because we are here discussing whether to convert from
struct
toclass
in order to get the benefit of supporting bothdouble
anddecimal
in the same library without doubling the code size.Fixes #354
Related issues
The text was updated successfully, but these errors were encountered: