Skip to content

Commit

Permalink
Merge pull request #6 from KlasMvW/use_rational_powers_only
Browse files Browse the repository at this point in the history
Use rational powers only
  • Loading branch information
KlasMvW authored Mar 15, 2024
2 parents 4a8f319 + 3ed902d commit 87c9439
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 359 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Powers are now rational numbers and not floating point values.

### Added

- New coherent unit `scalar` that can be used in binary operations with units.
- New constructor `Coherent_unit(TU_TYPE)` so that a coherent unit can be created with value.
- New prefixes `quecto`, `ronto`, `ronna`, `quetta`.

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.12)

project(TU VERSION 0.1.0 LANGUAGES CXX)
project(TU VERSION 0.2.0 LANGUAGES CXX)

enable_testing()
add_subdirectory(typesafe_units)
Expand Down
108 changes: 69 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@ Unit<prefix::no_prefix, coulomb> c = s * a;
std::cout << c.value << std::endl; // prints 5e-08
```
If you need a non-SI unit you define it by declaring a simple struct. This is how you would define the unit
`degree_Fahrenheit`:
If you need a non-SI unit you define it by declaring a Non_coherent_unit. This is how you would define the unit `degree_Fahrenheit`:
```c++
struct degree_Fahrenheit : Non_coherent_unit<1.0f / 1.8f, -32.0f, degree_Celsius> {
using Non_coherent_unit<1.0f / 1.8f, -32.0f, degree_Celsius>::Base;
};
using degree_Fahrenheit = Non_coherent_unit<1.0f / 1.8f, -32.0f, degree_Celsius>;
```

You can use the new unit like any other unit already defined in TU:
Expand Down Expand Up @@ -71,9 +68,7 @@ target_compile_definitions(my_target PRIVATE TU_TYPE=double)

## Requirements

TU requires a c++20 compliant compiler. Specifically TU utilizes float non-type template arguments.

For the test suite that comes with TU to work, your system needs to have support for ANSI escape sequences since the output uses colours. This should work on fairly recent Windows 10 system, linux and macOS. It might be a problem on Windows 7 though. If you find that this is a showstopper for you please let us know. If enough people run TU on systems that does not have support for ANSI escape sequences, we will remove it.
TU requires a c++20 compliant compiler. For the test suite that comes with TU to work, your system needs to have support for ANSI escape sequences since the output uses colours. This should work on fairly recent Windows 10 system, linux and macOS. It might be a problem on Windows 7 though. If you find that this is a showstopper for you please let us know. If enough people run TU on systems that does not have support for ANSI escape sequences, we will remove it.

## Tested compilers

Expand Down Expand Up @@ -153,7 +148,7 @@ TU uses official spelling of units. Therefore TU uses `litre` and `metre` and no
### Types

The intrinsic data type used by TU is defined in the preprocessor macro `TU_TYPE`.
`TU_TYPE` can be `float` or `double`. All values and floating point template argumets will have the type defined by `TU_TYPE`.
`TU_TYPE` can be `float` or `double`. All values will have the type defined by `TU_TYPE`.

### Namespaces

Expand Down Expand Up @@ -193,20 +188,22 @@ Base units are the smallest building blocks in the types system. These define po
The definition of base units are done through inheritance of the `Base_unit` struct and looks as follows where the base unit is denoted `X`.

```c++
template<TU_TYPE p>
template<Ratio p>
struct X : internal::Base_unit<p>{};
```
The definition of `s` (second) to some power `p` then looks as
```c++
template<TU_TYPE p>
template<Ratio p>
struct s : internal::Base_unit<p>{};
```

where Ratio is of type std:ratio

and a base unit `per_second` can be declared through

```c++
s<(TU_TYPE)-1.0f>;
s<std::ratio<-1>;
```

#### Coherent_unit
Expand All @@ -217,7 +214,13 @@ A specific coherent unit should be defined by inheriting from a `Coherent_unit`
The specific coherent unit `newton` is defined as

```c++
struct newton: Coherent_unit<s<(TU_TYPE)-2.0>, m<(TU_TYPE)1.0>, kg<(TU_TYPE)1.0>, A<(TU_TYPE)0.0>, K<(TU_TYPE)0.0>, mol<(TU_TYPE)0.0>, cd<(TU_TYPE)0.0>>{};
using newton = Coherent_unit<s<std::ratio<-2>
,m<std::ratio<1>
,kg<std::ratio<1>
,A<std::ratio<0>
,K<std::ratio<0>
,mol<std::ratio<0>
,cd<std::ratio<0>>;
```

i.e. it has the unit `kg m / s^2`
Expand All @@ -227,7 +230,13 @@ Note that computations using seconds should use the `Coherent_unit` `second` and
`second` is defined as

```c++
struct second: Coherent_unit<s<(TU_TYPE)1.0>, m<(TU_TYPE)0.0>, kg<(TU_TYPE)0.0>, A<(TU_TYPE)0.0>, K<(TU_TYPE)0.0>, mol<(TU_TYPE)0.0>, cd<(TU_TYPE)0.0>>{};
using second = Coherent_unit<s<std::ratio<1>
,m<std::ratio<0>
,kg<std::ratio<0>
,A<std::ratio<0>
,K<std::ratio<0>
,mol<std::ratio<0>
,cd<std::ratio<0>>;
```

All base units are defined as `Coherent_unit`s in similar fashion.
Expand All @@ -236,31 +245,23 @@ All base units are defined as `Coherent_unit`s in similar fashion.

A `Non_coherent_unit` is a unit that is scaled or shifted relative to a base unit. The value of the `Non_coherent_unit` is related to the value of the base unit through `v = a * b + c` where `v` is the value of the `Non_coherent_unit`, `b` is the value of the base unit. `a` and `c` are the scaling and shift respectively.

Example of `Non_coherent_unit`s are `minute`, `hour` and `degree_Celcius`.
Examples of `Non_coherent_unit`s are `minute`, `hour` and `degree_Celcius`.

A `Non_coherent_unit` is a templated struct that has the scaling factor, the shift and the base unit as template parameters.

`minute` and `hour` are defined by

```c++
struct minute : Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, second> {
using Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, second>::Base;
};
using minute = Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, second>;

struct hour : Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, minute> {
using Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, minute>::Base;
};
struct hour = Non_coherent_unit<(TU_TYPE)60.0, (TU_TYPE)0.0, minute>;
```
`degree_Celsius` is defined by
```c++
struct degree_Celsius : Non_coherent_unit<(TU_TYPE)1.0, (TU_TYPE)273.15, kelvin> {
using Non_coherent_unit<(TU_TYPE)1.0, (TU_TYPE)273.15, kelvin>::Base;
};
struct degree_Celsius = Non_coherent_unit<(TU_TYPE)1.0, (TU_TYPE)273.15, kelvin>;
```

The `using` statement is of internal concern only. If new `Non_coherent_unit`s are created, just follow the pattern.

#### Unit

The `Unit` is the intended public unit type.
Expand Down Expand Up @@ -298,6 +299,8 @@ std::cout << ms.value << " " << mi.value << std::endl; // prints 5.0 8.3333e-5

The following prefixes are defined and can be used when creating `Unit`s.

* quecto = 10<sup>-30</sup>
* ronto = 10<sup>-27</sup>
* yocto = 10<sup>-24</sup>
* zepto = 10<sup>-21</sup>
* atto = 10<sup>-18</sup>
Expand All @@ -319,6 +322,8 @@ The following prefixes are defined and can be used when creating `Unit`s.
* exa = 10<sup>18</sup>
* zetta = 10<sup>21</sup>
* yotta = 10<sup>24</sup>
* ronna = 10<sup>27</sup>
* quetta = 10<sup>30</sup>

### Functions

Expand Down Expand Up @@ -368,14 +373,20 @@ std::cout << cu.base_value << std::endl; // prints 3900.0
This is because TU does not know what `Unit` to construct from the operation. TU falls back on the fundamental `Coherent_unit`s and `cu` will be of type

```c++
Coherent_unit<s<(TU_TYPE)1.0>, m<(TU_TYPE)0.0>, kg<(TU_TYPE)0.0>, A<(TU_TYPE)0.0>, K<(TU_TYPE)0.0>, mol<(TU_TYPE)0.0>, cd<(TU_TYPE)0.0>>;
Coherent_unit<s<std::ratio<1>
,m<std::ratio<0>
,kg<std::ratio<0>
,A<std::ratio<0>
,K<std::ratio<0>
,mol<std::ratio<0>
,cd<std::ratio<0>>;
```

Note also that `Coherent_unit` does not have a `value` member but only a `base_value`

If we would like a specific `Unit` representation of the operation, we have to explicitly state the `Unit` as in the first example and the result of the operation will be used to construct the desired `Unit`.

Applying the `+` and `-` operators on `Unit`s that don't have the same underlying `Coherent_unit` will result in compilation failure e.g. it is not possible to add to variables of type `newton` and `second`.
Applying the `+` and `-` operators on `Unit`s that don't have the same underlying `Coherent_unit` will result in compilation failure e.g. it is not possible to add two variables of type `newton` and `second`.

#### \* /

Expand All @@ -400,7 +411,13 @@ std::cout << cu.base_value << std::endl; // prints 5e-08
This is because TU does not know what `Unit` to construct from the operation. TU falls back on the fundamental `Coherent_units` and `cu` will be of type

```c++
Coherent_unit<s<(TU_TYPE)1.0>, m<(TU_TYPE)0.0>, kg<(TU_TYPE)0.0>, A<(TU_TYPE)1.0>, K<(TU_TYPE)0.0>, mol<(TU_TYPE)0.0>, cd<(TU_TYPE)0.0>>;
Coherent_unit<s<std::ratio<1>
,m<std::ratio<0>
,kg<std::ratio<0>
,A<std::ratio<1>
,K<std::ratio<0>
,mol<std::ratio<0>
,cd<std::ratio<0>>;
```

Note also that `Coherent_unit` does not have a `value` member but only a `base_value`
Expand All @@ -426,21 +443,27 @@ TU implements a `pow` operator for units.
```c++
Unit<prefix::milli, metre> me(5.0f);
auto ch = pow<2.0f>(me);
auto ch = pow<std::ratio<2>>(me);
std::cout << ch.base_value << std::endl; // prints 2.5 * 10^-5
```

`ch` will be of type

```c++
Coherent_unit<s<(TU_TYPE)0.0>, m<(TU_TYPE)2.0>, kg<(TU_TYPE)0.0>, A<(TU_TYPE)0.0>, K<(TU_TYPE)0.0>, mol<(TU_TYPE)0.0>, cd<(TU_TYPE)0.0>>;
Coherent_unit<s<std::ratio<0>
,m<std::ratio<2>
,kg<std::ratio<0>
,A<std::ratio<0>
,K<std::ratio<0>
,mol<std::ratio<0>
,cd<std::ratio<0>>;
```

To construct a `Unit` directly we could do

```c++
Unit<prefix::milli, metre> me(5.0f);
Unit<prefix::milli, metre_squared> m2 = pow<2.0f>(me);
Unit<prefix::milli, metre_squared> m2 = pow<std::ratio<2>>(me);
std::cout << m2.value << std::endl; // prints 2.5 * 10^-2
```
Expand All @@ -449,7 +472,7 @@ Note that `Unit<prefix::milli, metre_squared>` means 10<sup>-3</sup>m<sup>2
To the unit </sup>(mm)<sup>2</sup> is equivalent to `Unit<prefix::micro, metre_squared>`
Note that the power is not restricted to integers.
Note that the power is restricted to std::ratio.
#### sqrt
Expand All @@ -461,21 +484,28 @@ sqrt(unit).
is equivalent to

```c++
pow<0.5>(unit).
pow<std::ratio<1,2>>(unit).
```

Note that since TU uses floating point powers, it is not guaranteed that applying first `pow<2.0>` and the `sqrt` on a unit would yield the exact unit back.

#### unop

TU supports unary operations on scalar units i.e. units where all basic unit powers are `0`. Examples of scalar units is `radian` and `degree`.

`unop` is a template function that applies any unary function that takes a TU_TYPE
and returns a TU_TYPE to the underlying value of the unit if it is a scalar unit. The function returns a scalar Coherent_unit initialized with the value of the performed operation. This makes it possible to operate with any unary function (subjected to the restrictions above) from the standard library on a Unit or Coherent_unit. unop can take both unary functions and lambda expressions as template parameter.
and returns a TU_TYPE to the underlying **base_value** of the unit if it is a scalar unit. The function returns a scalar Coherent_unit initialized with the value of the performed operation. This makes it possible to operate with any unary function (subjected to the restrictions above) from the standard library on a Unit or Coherent_unit. unop can take both unary functions and lambda expressions as template parameter.

```c++
Unit<prefix::no_prefix, degree> angle(90);
std::cout << unop<std::sin>(angle).base_value; // prints 1
Unit<prefix::no_prefix, degree> angle_d(90);
std::cout << unop<std::sin>(angle_d).base_value; // prints 1

Unit<prefix::no_prefix, radian> angle_r(PI);
std::cout << unop<std::sin>(angle_r).base_value; // prints 1

constexpr auto lambda = [](TU_TYPE v) {
return v + (TU_TYPE)1.0;
};

std::cout << unop<lambda>(angle_d).base_value; // prints 2.5708 i.e. PI/2.0 + 1.0
```
Note that `unop` operates on the `base_value` on a unit. In the case of `degree` the base unit is `radian` (90 degrees == pi/2 radians) and the `std::sin` function yields the correct result.
Expand Down
53 changes: 42 additions & 11 deletions TUConfigVersion.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,52 @@
# The variable CVF_VERSION must be set before calling configure_file().


set(PACKAGE_VERSION "0.1.0")
if (PACKAGE_FIND_VERSION_RANGE)
message(AUTHOR_WARNING
"`find_package()` specify a version range but the version strategy "
"(ExactVersion) of the module `${PACKAGE_FIND_NAME}` is incompatible "
"with this request. Only the lower endpoint of the range will be used.")
endif()

set(PACKAGE_VERSION "0.2.0")

if("0.2.0" MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") # strip the tweak version
set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(CVF_VERSION_MINOR "${CMAKE_MATCH_2}")
set(CVF_VERSION_PATCH "${CMAKE_MATCH_3}")

if("0.1.0" MATCHES "^([0-9]+\\.[0-9]+\\.[0-9]+)\\.") # strip the tweak version
set(CVF_VERSION_NO_TWEAK "${CMAKE_MATCH_1}")
if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}")
endif()
if(NOT CVF_VERSION_MINOR VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" CVF_VERSION_MINOR "${CVF_VERSION_MINOR}")
endif()
if(NOT CVF_VERSION_PATCH VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" CVF_VERSION_PATCH "${CVF_VERSION_PATCH}")
endif()

set(CVF_VERSION_NO_TWEAK "${CVF_VERSION_MAJOR}.${CVF_VERSION_MINOR}.${CVF_VERSION_PATCH}")
else()
set(CVF_VERSION_NO_TWEAK "0.1.0")
set(CVF_VERSION_NO_TWEAK "0.2.0")
endif()

if(PACKAGE_FIND_VERSION MATCHES "^([0-9]+\\.[0-9]+\\.[0-9]+)\\.") # strip the tweak version
set(REQUESTED_VERSION_NO_TWEAK "${CMAKE_MATCH_1}")
if(PACKAGE_FIND_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") # strip the tweak version
set(REQUESTED_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(REQUESTED_VERSION_MINOR "${CMAKE_MATCH_2}")
set(REQUESTED_VERSION_PATCH "${CMAKE_MATCH_3}")

if(NOT REQUESTED_VERSION_MAJOR VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" REQUESTED_VERSION_MAJOR "${REQUESTED_VERSION_MAJOR}")
endif()
if(NOT REQUESTED_VERSION_MINOR VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" REQUESTED_VERSION_MINOR "${REQUESTED_VERSION_MINOR}")
endif()
if(NOT REQUESTED_VERSION_PATCH VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" REQUESTED_VERSION_PATCH "${REQUESTED_VERSION_PATCH}")
endif()

set(REQUESTED_VERSION_NO_TWEAK
"${REQUESTED_VERSION_MAJOR}.${REQUESTED_VERSION_MINOR}.${REQUESTED_VERSION_PATCH}")
else()
set(REQUESTED_VERSION_NO_TWEAK "${PACKAGE_FIND_VERSION}")
endif()
Expand All @@ -34,11 +70,6 @@ if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
endif()


# if the installed project requested no architecture check, don't perform the check
if("FALSE")
return()
endif()

# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
return()
Expand Down
Loading

0 comments on commit 87c9439

Please sign in to comment.