KotUniL covers all units of International System of Units (SI)
like meter, second etc. (see Wikipedia)
as well as SI- Prefixes (micro, nano etc.) and some other common units like currencies, percentages etc.
KotUniL (Kotlin Units Library) a library of functions and objects of Kotlin that meet the following requirements in total:
- It allows various formulas to be written in Kotlin in a way maximally similar to the way formulas are written in physics and economics.
- It allows analyzing dimensions of results of applications of complicated formulas.
- It allows to detect most of the typical errors when working with SI units already at the compilation stage. Errors in incorrect use of physical units in complex formulas are detected in runtime.
- It is pure library (no plug-in, no parser etc.), it has no dependencies
The goal of the project is not only to provide a unified set of objects and functions that allows to use the SI in programs in different programming languages. The project also contains simple applications in all of the target languages that show how to do this. In addition, these applications test the availability of all necessary functions on that target platform.
(So far, applications for only two target platforms have been implemented.)
Therefore, all subprojects of this project are divided into three groups:
- kotunil - implementation of library functionality in Kotlin.
- js-lib - JavaScript variant of KotUniL, available as NPM package kotunil-js-lib.
- apps applications that test the functionality of the library on a specific platform. In the moment are test applications as console-application for Kotlin and Java platfform impemented ans web page for JavaScript variant od library.
- kotunil-generators - generators used to accelerate the implementation and testing of the library.
If you are a developer and you are interested in the source code of the library, you only need part kotunil.
Let's consider some simple examples.
In Eva's case, a glass broke in her aquarium and water flowed on floor. In aquarium before breakage was 32 liters of water. Eva's room is 4 m. wide and 4.3 long. How high in mm. is water now in Eva's room with assumption that it stayed there and did not flow away?
The solution in Kotlin can be written in one line. For didactic reasons like introduce two auxiliary variables s and h.
val s = 4.m * 4.3.m
val h = 32.l/s
print("Water height is ${h.mm} mm.")
KotUniL is a multiplatform library. You can read about how to properly write physics and other formulas using the objects and functions of the KotUniL library in document "Rules for writing KotUniL formulas in different programming languages".
Please note that the variable s is physical dimension m2 (square meter) and h - m (simple meter). Furthermore, in this example we also used liters (l), which have dimension m3 (cubic meters).
Using the built-in function unitSymbols() you can determine the dimension of any object in terms of SI standard.
val s = 4.m * 5.m
assertEquals("m2", s.unitSymbols())
val x = 20.l
assertEquals("m3", x.unitSymbols())
val h = x/s
assertEquals("m", h.unitSymbols())
val y = 1.2.s
assertEquals("s", y.unitSymbols())
val z = x/y
assertEquals("m3/s", z.unitSymbols())
Using the built-in function categorySymbols() you can analyze dimensions of physical units in "academic" manner.
val s = 4.m * 5.m
assertEquals("L2", s.categorySymbols())
val x = 20.l
assertEquals("L3", x.categorySymbols())
val h = x/s
assertEquals("L", h.categorySymbols())
val y = 1.2.s
assertEquals("T", y.categorySymbols())
val z = x/y
assertEquals("L3T-1", z.categorySymbols())
Physical units of the same dimension can be added, subtracted and compared. If you try to do this with units of different types, you will get either compilation errors (for simple units) or run-time errors for complicated units.
//val x = 1.m + 2 compilation error
//val y = 20.l/(4.m * 5.m) + 14 compilation error
//Complex errors will be found in runtime:
val exception = assertFailsWith<IllegalArgumentException>(
block = { 1.m + 2.s }
)
assertTrue(exception.message!!.startsWith(COMPATIBILITY_ERR_PREFIX))
Of course, a safety with an exception is not a classic type safety, that shows up during compilation time. However, it is better than letting the metre and the second add up.
When using SI such error is found during the first unit test.
(Reminder: just such errors led in the past to terrible technical catastrophes,
see e.g. https://en.wikipedia.org/wiki/Mars_Climate_Orbiter).
Physical objects can be compared only if they have the same dimensions.
assertTrue(5.m > 4.1.m)
assertTrue(20.2*m3 > 4.2*m3)
assertTrue(2.2*kg*m/s < 4.2*kg*m/s)
Otherwise, you will get a run-time error.
val v1 = 2.4.m
val v2 = 2.4.s
val exception = assertFailsWith<IllegalArgumentException>(
block = { v1 >= v2 }
)
assertTrue(exception.message!!.startsWith(COMPATIBILITY_ERR_PREFIX))
or:
val v1 = 2.4.m*kg/s
val v2 = 2.4.s*m3/ μV
val exception = assertFailsWith<IllegalArgumentException>(
block = { v1 >= v2 }
)
assertTrue(exception.message!!.startsWith(COMPATIBILITY_ERR_PREFIX))
With KotUniL you can also compare, add and divide the objects that are defined differently, but have the same physical dimension.
In the example below the objects with the dimension "hertz" (1/second) are used.
assertEquals(0.9.Hz, 0.9/s)
assertEquals(0.9.Hz, 5/s - 4.1.Hz)
KotUniL distributed as Kotlin-library via MavenCentral. Dependency for build.gradle.kts:
repositories {
mavenCentral()
}
dependencies {
implementation("eu.sirotin.kotunil:all:3.0.0")
}
Dependency for Maven/pom:
<dependency>
<groupId>eu.sirotin.kotunil</groupId>
<artifactId>all</artifactId>
<version>3.0.0</version>
</dependency>
At the moment KotUniL is developing rapidly. To enable you to try the latest features of KotUniL use the following configuration at KTS:
implementation("eu.sirotin.kotunil:all:+")
and by Maven:
<version>LATEST</version>
If you want to use it as source code, copy into your project all production files from src/commonMain/kotlin/eu/sirotin/kotunil.
SI standard defines 7 base units, as well as some derived units and accepts historically established non-SI units.
The table below listed Base SI units.
You can define a physical unit by multiplying a value at unit variable or from a number (Double, Int etc.) if you write SI symbol after a dot after a number.
1*s == 1.0.s
s == 1.s
s = 1.0.s
In special cases you can also create unit using a class constructor.
Second(1.0) == 1.s
SI Base Unit | Unit symbol |
---|---|
second | s |
metre | m |
kilogram | kg |
ampere | A |
kelvin | K |
mole | mol |
candela | cd |
The name of the class differs from the name of the unit by first capital letter.
SI standard defines beside base unit also 22 derived units. They are listed in the table below. Do not be surprised if you encounter in this table and in KotUniL library unusual for identifiers symbols like Ω. It is possible because according to Kotlin's specification most common unicode symbols can be used in Kotlin identifiers (see paragraph "1.2.4 Identifiers" in Kotlin Specification https://kotlinlang.org/spec/pdf/kotlin-spec.pdf). Derived units can be used just like base units. Both types can be used in a formula by multiplication or division.
SI Derived Unit | Unit symbol | Formula |
---|---|---|
radian | rad | m/m |
steradian | sr | m2/m2 |
hertz | Hz | 1/s |
newton | N | kg*m/(s ^ 2) |
pascal | Pa | kg/(m * (s ^ 2)) |
joule | J | kg*(m2)/(s ^ 2) |
watt | W | kg*(m2)/(s ^ 3) |
coulomb | C | s*A |
volt | V | kg*m2*(s ^ -3) * (A ^ -1) |
farad | F | (kg ^ -1) * (m ^ -2) * (s ^ 4) * (A ^ 2) |
ohm | Ω | kg*m2 * (s ^ -3) * (A ^ -2) |
siemens | S | (kg ^ -1) * (m ^ -2) *(s ^ 3)* (A ^ 2) |
weber | Wb | kg*(m2) * (s ^ -2) * (A ^ -1) |
tesla | T | kg* (s ^ -2) * (A ^ -1) |
henry | H | kg* (m2)*(s ^ -2)*(A ^ -2) |
degreeCelsius | Celsius | (K ^ 1) |
lumen | lm | ((cd ^ 1)*sr) |
lux | lx | cd*sr*(m ^ -2) |
becquerel | Bq | (s ^ -1) |
gray | Gy | (m2)*(s ^ -2) |
sievert | Sv | (m2)*(s ^ -2) |
katal | kat | (mol * (s ^ -1)) |
Also, do not wound yourself when you see the ^ symbol. Kotlin has no operator for power, but a function. KotUniL was extended with this infix function. Unfortunately, with it can not set proper priority for this function. Although this looks like a "real" operator, for keeping proper operation prioritization, you have to put it in parentheses.
Miller family makes a trip to the nature. They brought a solar panel and immediately turned on at the excursion site. Solar produced 12 volts and 7 amps 2 hours. Produced electricity was stored in a storage tank. Storage efficiency is 85%. After that, Mrs. Miller decided to prepare the tea in boiler. To prepare hot water for tea with boilers with 500 watt strength, the water should be boiled for 8 min. The question, is stored in memory electricity enough for that?
val producedElectricity = 12.V * 7.A * 2.h
val savedElectricity = producedElectricity * 85.`%`
val neededElectricity = 0.5.kW * 8.min
val dif = savedElectricity - neededElectricity
assertTrue(dif < 0.W*h) //Comparison in KotUniL
assertTrue(dif.value < 0) //Comparison dimensionless
Note that for convenience we use here the symbol %
.
This is also extension of Kotlin in our library.
Regarding this and similar extensions, see the statement: "Kotlin supports escaping identifiers by enclosing any sequence of characters into backtick (`) characters, allowing to use any name as an identifier." (See paragraph 1.2.4 "Identifiers" in Kotlin specification https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-Identifier). The character backtick (`) (UTF-8 code U+0060) is not equal to the apostrophe characters on the keyboard (UTF-8 code U+0027). To use it, you better copy it directly from this example.
Please note: such identifiers currently work only on Jvm platform.
Most derived units can be derived from Based Units
or from other Derived Units in different ways.
Look at the example of Tesla:
assertEquals(T, kg * (s `^` -2) * (A `^` -1))
assertEquals(T, Wb/ m2)
You can also define your own Derived Units.
Consider a not properly scientifically proven example.
Let's imagine that the melting speed of snow in mountains
is proportional to the duration and temperature above 0 °C.
This will be our new Derived Unit.
If current snow thickness is 10 cm,
what proportion will be melted in 5 hours at 20 °C?
The code below also shows the nice side of Kotlin -
a possibility to use Unicode symbols, e.g. Greek letters.
val ζ = 10.μm/(h*K) //melting speed
val τ = 10.cm
val t = 20.`°C`
val ξ = 5.h*(t - 0.`°C`)
val σ = ζ*ξ //melting height
val α = σ/τ //melting ration
assertEquals(1.0, α.`as %`, ε) //α.`as %` - Ratio presented in percents
Please note, how you can present calculation result in percents:
α.as %
- ratio presented in percents.
ε is some small value for tolerance by comparison of double numbers. This is also one of the variables defined in KotUniL
SI standard also defines SI prefixes and rules for their use with units ( Base and Derived). SI Units library implements this mechanism. The table of prefixes and their values can be seen in the table below.
Prefix | Symbol | Degree |
---|---|---|
quetta | Q | 30 |
ronna | R | 27 |
yotta | Y | 24 |
zetta | Z | 21 |
exa | E | 18 |
peta | P | 15 |
tera | T | 12 |
giga | G | 9 |
mega | M | 6 |
kilo | k | 3 |
hecto | h | 2 |
deca | da | 1 |
deci | d | -1 |
centi | c | -2 |
milli | m | -3 |
micro | μ | -6 |
nano | n | -9 |
pico | p | -12 |
femto | f | -15 |
atto | a | -18 |
zepto | z | -21 |
yocto | y | -24 |
ronto | r | -27 |
quecto | q | -30 |
In example below we check that one kilometer is equal to milliard of micrometer.
val d = km - (10 `^` 9) * μm
assertTrue(abs(d.value) < ε)
Many non-SI units continue to be used in the scientific, technical, and commercial literature. KotUniL Library implements these units. They are listed in the table below.
Quantity | Name | Symbol | Value in SI units |
---|---|---|---|
time | minute | min | 1 min = 60 s |
time | hour | h | 1 h = 60 min = 3600 s |
time | day | d | 1 d = 24 h = 86400 s |
length | astronomical unit | au | 1 au = 149597870700 m |
plane and phase angle | degree | ° | 1° = π*rad/180 |
plane and phase angle | arcminute | ′ | 1′ = 1°/60 = π*rad/10800 |
plane and phase angle | arcsecond ″ 1″ = 1′ /60 = π*rad/648000 | ||
area | hectare | ha | 1 ha = 1 hm2 = 10^4 m2 |
volume | litre | l, L | 1 l = 1 L = 1 dm3 = 10^3 cm3 = 10^−3 m3 |
mass | tonne (metric ton) | t | 1 t = 1 Mg = 10^3 kg |
mass | dalton | Da | 1 Da = 1.660539040(20)×10^−27 kg |
energy | electronvolt | eV | 1 eV = 1.602176634×10^−19 J |
Example:
A city park has area 2.3 hectares.
During a rain 1 mm of water had fallen from the sky.
If there was no rain, the park should be watered
with water from car cisterns. A car cistern can carry 4 tons of water.
How many cisterns are needed to achieve
the same effect as in case of rain?
Reminder: density of watter is 1 kg/l
val s = 1.ha
val ω = s*100.mm //water volume
val ρ = kg/l //density of watter is 1 kg/l
val τ = ω * ρ //common water weight of rain
val n = τ/4.t
assertEquals(250.0, n.value, ε)
KotUniL library lets you use currencies in your calculations.
Some example:
A householder has decided to cover the floor with tiles in one of his rooms.
He has bought 16,5 sqm of tiles for 52 €/sqm. How much does he pay for his tiles?
val prise = 52.`€`/m2
val s = 16.5*m2
val cost = s*prise
assertEquals("858,00 EUR", cost.show("%.2f"))
assertEquals("EUR", cost.unitSymbols())
Sometimes you need an abstract unit like "thing". For example:
Every good guy has 30 different things per liter in his pocket. Jan is a good guy and his pocket is 0.3 liters big. How many things can his mom find in Jan's pocket?
val p = 30.`#`/l
val n = 0.3.l * p
assertEquals("9 #", n.show("%.0f"))
assertEquals("#", n.unitSymbols())
Library completely implements Kotlin's operators like: Unare operators:
a = +b
a = -b
Augment assignments:
a +=b
a -=b
a *=b
a /=b
a %=b
Attention: Operators
a++
and
a--
are not implemented due to known bugs Kotlin compiler (See https://youtrack.jetbrains.com/issue/KT-24800)
Head image: Genesis rules of International System of Units (Source: Wikipedia)