-
Notifications
You must be signed in to change notification settings - Fork 393
Adding a New Unit
So you want to add a quantity or unit that is not yet part of Units.NET?
- Great, but before you start!
- Requirements
- Quick Summary of Steps
- Detailed steps
- Logarithmic Units
- Code Style
Sometimes we just have to say no, sorry! 😅 We simply want to avoid bloating the library.
- Is widely used.
- Has multiple units to convert between (e.g.
Length
has kilometer, feet, nanometer etc.) - Can be represented by a
double
numeric value, integer values are not well supported and may suffer from precision errors. - Is not dimensionless/unitless (consider using
Ratio
)
- Is widely used.
- Can be converted to other units of the same quantity.
- The conversion function is well established without ambiguous competing standards.
We have made some exceptions to these "rules" so start a discussion with us if you still think it belongs in the library.
Ok, enough of that. Let's move on! 😃
In order to build and run tests locally you need to have some tools installed.
-
.NET Core SDK 2.1+
to generate and build code
Units.NET uses CodeGen, a C# command line app that reads JSON files with quantity and unit definitions and generates C# code.
To add a quantity or a unit:
- Add or change a quantity JSON file.
- Run
generate-code.bat
file. - Specify test values for the new units in the generated test code.
Easy peasy. Below are the detailed steps.
- Place in Common/UnitDefinitions
- See Length.json as an example.
- Multiply for
FromBaseToUnit
and divide forFromUnitToBase
, so thatLength.Centimeter
is defined as"FromBaseToUnit": "x*100"
and"FromUnitToBase": "x/100"
where base unit isMeter
- Prefer
1e3
and1e-5
notation instead of1000
and0.00001
- Prefer a constant if the conversion factor is finite and well known (
Inch FromUnitToBase: x*2.54e-2
) - Prefer a calculation if the conversion factor is infinite (
PrinterPoint FromUnitToBase: (x/72.27)*2.54e-2
). If the calculation is not available, specify the most precise constant you can.double
numeric type can represent 15-17 significant decimal digits
The base unit dimensions of the quantity, such as "L": 1
for Length
and "L": 2
for Area
(Length*Length
).
The 7 SI base units are:
-
L
- Length -
M
- Mass -
T
- Time -
I
- ElectricCurrent -
Θ
- Temperature -
N
- AmountOfSubstance -
J
- LuminousIntensity
when converting from one unit to another with FromUnitToBaseFunc
and FromBaseToUnitFunc
conversion functions. It is typically chosen as an SI derived unit (Meter
, Newtonmeter
, Squaremeter
etc). This choice affects the precision of conversions for much bigger/smaller units than BaseUnit
.
BaseUnits
(optional) - the SI base units of a unit
Don't confuse this with the quantity's BaseUnit
, which is discussed to be renamed.
If specified, you can create quantities with consistent units for a given unit system:
new Length(1, UnitSystem.SI).ToString() // "1 m"
new Length(1, myBritishEngineeringUnitSystem).ToString() // "1 ft"
-
LengthUnit.Inch
has{ "L": "Inch" }
(L=1) -
AreaUnit.SquareCentimeter
has{ "L": "Centimeter" }
, because we ignore dimensions (L=2) -
VolumeUnit.Cubicfeet
has{ "L": "Foot" }
, because we ignore dimensions (L=3) -
ForceUnit.Newton
has{ "L": "Meter", "M": "Kilogram", "T": "Second" }
, becauseN = 1 kg * 1 m / s² = Kilogram * Meter / Second²
and we ignore the dimensions -
ForceUnit.PoundForce
has{ "L": "Foot", "M": "Pound", "T": "Second" }
, becauseN = 1 lbm * 1 ft / s² = Pound * Foot / Second²
and we ignore the dimensions -
MassConcentrationUnit.GramPerDeciliter
has{ "L": "Centimeter", "M": "Gram" }
, becauseDeciliter = 1 cm * 1cm * 1cm = Centimeter³
and we ignore the dimensions
The only consequence of not specifying BaseUnits
is that you cannot construct these units by passing a UnitSystem
to the quantity constructor as in the example above.
-
VolumeUnit.ImperialGallon
has noBaseUnits
, becauseVolume = Length³
and there is no length unit that when multiplied three times would result in imperial gallon. -
RatioUnit.DecimalFraction
has noBaseUnits
, because dimensionless units are not made up by any SI base units.
2. Run generate-code.bat
to generate unit classes, unit enumerations and base class for tests.
This step might no longer be necessary, I think Visual Studio 2017 and the new .csproj format automatically loads new files automatically.
- Override the missing abstract properties in the unit test class (ex: LengthTests.cs)
- Specify value as a constant, not a calculation, with preferably at least 7 significant figures where possible.
- Triple-check the number you write here, this is the most important piece as it verifies your conversion function in the .JSON file
- Example:
InchesInOneMeter
in LengthTests.cs- I find the conversion factor to be
39.37007874
from an online unit conversion calculator, it has 10 significant figures so that is plenty - I add the code:
protected override double InchesInOneMeter => 39.37007874;
- I Google to double-check:
Inches In 1 Meter
and it tells me1 Meter = 39.3701 Inches
(Google typically has fewer significant figures) - If Google can't help me, I find a second source to confirm the conversion factor (another conversion website, wikipedia, Wolfram Alpha etc)
- I check again by intuition, is there really around 40 inches in a meter? Yes, sounds about right.
- I find the conversion factor to be
Make sure all the tests pass. Either run build.bat or run the tests from within Visual Studio with ReSharper or the built-in test runner.
Please see GitHub: Creating a pull request. If you still have any questions, please ask on our Gitter chat or create an issue.
Units.NET supports logarithmic units by adding Logarithmic
and LogarithmicScalingFactor
(optional) properties.
-
LogarithmicScalingFactor
is used to provide a scaling factor in the logarithmic conversion. For example, a scaling factor of2
is required when implementing the ratio of the squares of two field amplitude quantities such as voltage. In most casesLogarithmicScalingFactor
will be1
.
To create a logarithmic unit, follow the same steps from the previous section making the following adjustments:
Step 1. Add property "Logarithmic": "True"
to the JSON file, just after BaseUnit
. LogarithmicScalingFactor
defaults to 1
if not defined.
Step 4. Provide custom implementations for logarithmic addition and subtraction unit tests. See LevelTests.cs for an example.
Refer to Level.json as an example implementation of logarithmic units.
- If you have the ReSharper plugin installed, there are code formatting settings checked into the repository that will take effect automatically.
- If you don't use ReSharper, at least follow the same conventions as in the existing code.