Giter Site home page Giter Site logo

uom's Introduction

uom

Github Actions Codecov.io Rustup.rs Crates.io Crates.io Documentation

Units of measurement is a crate that does automatic type-safe zero-cost dimensional analysis. You can create your own systems or use the pre-built International System of Units (SI) which is based on the International System of Quantities (ISQ) and includes numerous quantities (length, mass, time, ...) with conversion factors for even more numerous measurement units (meter, kilometer, foot, mile, ...). No more crashing your climate orbiter!

Usage

uom requires rustc 1.65.0 or later. Add this to your Cargo.toml:

[dependencies]
uom = "0.36.0"

and this to your crate root:

extern crate uom;

The simple example below shows how to use quantities and units as well as how uom stops invalid operations:

extern crate uom;

use uom::si::f32::*;
use uom::si::length::kilometer;
use uom::si::time::second;

fn main() {
    let length = Length::new::<kilometer>(5.0);
    let time = Time::new::<second>(15.0);
    let velocity/*: Velocity*/ = length / time;
    let _acceleration = calc_acceleration(velocity, time);
    //let error = length + time; // error[E0308]: mismatched types

    // Get a quantity value in a specific unit.
    let time_in_nano_seconds = time.get::<uom::si::time::nanosecond>();
}

fn calc_acceleration(velocity: Velocity, time: Time) -> Acceleration {
    velocity / time
}

See the examples directory for more advanced usage:

  • si.rs -- Shows how to use the pre-built SI system.
  • base.rs -- Shows how to create a set of Quantity type aliases for a different set of base units. See the Design section for implications of choosing different base units.
  • mks.rs -- Shows how to create a custom system of quantities.
  • unit.rs -- Shows how to add new units to existing quantities in the pre-build SI system.

Features

uom has multiple Cargo features for controlling available underlying storage types, the inclusion of the pre-built International System of Units (SI), support for Serde, and no_std functionality. The features are described below. f32, f64, std, and si are enabled by default. Features can be cherry-picked by using the --no-default-features and --features "..." flags when compiling uom or specifying features in Cargo.toml:

[dependencies]
uom = {
    version = "0.36.0",
    default-features = false,
    features = [
        "autoconvert", # automatic base unit conversion.
        "usize", "u8", "u16", "u32", "u64", "u128", # Unsigned integer storage types.
        "isize", "i8", "i16", "i32", "i64", "i128", # Signed integer storage types.
        "bigint", "biguint", # Arbitrary width integer storage types.
        "rational", "rational32", "rational64", "bigrational", # Integer ratio storage types.
        "complex32", "complex64", # Complex floating point storage types.
        "f32", "f64", # Floating point storage types.
        "si", "std", # Built-in SI system and std library support.
        "serde", # Serde support.
    ]
}
  • autoconvert -- Feature to enable automatic conversion between base units in binary operators. Disabling the feature only allows for quantities with the same base units to directly interact. The feature exists to account for compiler limitations where zero-cost code is not generated for non-floating point underlying storage types.
  • usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, bigint, biguint, rational, rational32, rational64, bigrational, complex32, complex64, f32, f64 -- Features to enable underlying storage types. At least one of these features must be enabled. f32 and f64 are enabled by default. See the Design section for implications of choosing different underlying storage types.
  • si -- Feature to include the pre-built International System of Units (SI). Enabled by default.
  • std -- Feature to compile with standard library support. Disabling this feature compiles uom with no_std. Enabled by default.
  • serde -- Feature to enable support for serialization and deserialization of quantities with the Serde crate. Disabled by default. Replaces the deprecated use_serde feature, which will be removed in a future uom release (v0.37.0 or later).

Design

Rather than working with measurement units (meter, kilometer, foot, mile, ...) uom works with quantities (length, mass, time, ...). This simplifies usage because units are only involved at interface boundaries: the rest of your code only needs to be concerned about the quantities involved. This also makes operations on quantities (+, -, *, /, ...) have zero runtime cost over using the raw storage type (e.g. f32).

uom normalizes values to the base unit for the quantity. Alternative base units can be used by executing the macro defined for the system of quantities (ISQ! for the SI). uom supports usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, bigint, biguint, rational, rational32, rational64, bigrational, complex32, complex64, f32, and f64 as the underlying storage type.

A consequence of normalizing values to the base unit is that some values may not be able to be represented or can't be precisely represented for floating point and rational underlying storage types. For example if the base unit of length is meter and the underlying storage type is i32 then values like 1 centimeter or 1.1 meter cannot be represented. 1 centimeter is normalized to 0.01 meter which can't be stored in an i32. uom only allows units to be used safely. Users of this library will still need to be aware of implementation details of the underlying storage type including limits and precision.

Contributing

Contributions are welcome from everyone. Submit a pull request, an issue, or just add comments to an existing item. The International Bureau of Weights and Measures is an international standards organization that publishes the SI Brochure. This document defines the SI and can be used as a comprehensive reference for changes to uom. Conversion factors for non-SI units can be found in NIST Special Publication 811.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as below, without any additional terms or conditions.

License

Licensed under either of

at your option.

uom's People

Contributors

adamreichold avatar aehmlo avatar apopiak avatar baarkerlounger avatar bheisler avatar calbaker avatar crystal-growth avatar dmit avatar eagle941 avatar gonzaponte avatar groscoe2 avatar hellow554 avatar iliekturtles avatar jacg avatar nemo157 avatar nick-pascucci-spire avatar nicodemus26 avatar niklasvousten avatar nvzqz avatar octronics avatar okkn avatar radix avatar robinohs avatar swaits avatar tobtobxx avatar uzaaft avatar waywardmonkeys avatar xvapx avatar yacinelakel avatar zdimension avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uom's Issues

Handle overflows in conversion factors

Handle overflows in conversion factors so that factors that exceed the underlying storage type's min/max values do not cause errors. Currently tests are failing because conversion factors overflow the underlying storage type.

  • Don't implement Conversion for types that can't represent the conversion factor? e.g. don't implement Conversion for kilometer when the underlying storage type is u8.
  • Implement Conversion but have tests skip factors that can't accurately be represented?

Implement float methods

https://doc.rust-lang.org/std/primitive.f32.html

Implement operations for Quantity references

impl Op<Quantity<...>> for Quantity<..> is already complete. Add the following reference implementations:

  • impl<'a, 'b> Op<&'a Quantity<...>> for &'b Quantity<..>
  • impl<'a> Op<&'a Quantity<...>> for Quantity<..>
  • impl<'a> Op<Quantity<...>> for &'a Quantity<..>

Implement wrapping operations / num_traits::ops::wrapping

  • wrapping_abs
  • wrapping_add
  • wrapping_div
  • wrapping_mul
  • wrapping_neg
  • wrapping_rem
  • wrapping_shl (?)
  • wrapping_shr (?)
  • wrapping_sub
  • num_traits::ops::wrapping::WrappingAdd
  • num_traits::ops::wrapping::WrappingMul
  • num_traits::ops::wrapping::WrappingSub

comparison of integral Quantities is very slow

The short version: comparing integral quantities with Eq can be many orders of magnitude slower than regular comparison. This is magnified for the bigger types, with i64 being about a few thousand times slower than comparing plain i64 values. I assume the same happens for the PartialOrd implementation.

     Running `C:\Users\radix\Projects\pandt\target\release\deps\uom_ops-6068a9413eff2c40.exe --bench`

simple u32              time:   [376.27 ps 383.53 ps 390.64 ps]
Found 4 outliers among 100 measurements (4.00%)
  3 (3.00%) high mild
  1 (1.00%) high severe

simple i64              time:   [366.59 ps 369.57 ps 372.66 ps]
Found 8 outliers among 100 measurements (8.00%)
  2 (2.00%) low mild
  3 (3.00%) high mild
  3 (3.00%) high severe

uom u32                 time:   [808.11 ns 814.27 ns 821.16 ns]
Found 9 outliers among 100 measurements (9.00%)
  3 (3.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe

uom i64                 time:   [1.5856 us 1.5981 us 1.6113 us]
Found 13 outliers among 100 measurements (13.00%)
  1 (1.00%) low severe
  4 (4.00%) low mild
  4 (4.00%) high mild
  4 (4.00%) high severe

Here's the benchmark:

#[macro_use] extern crate criterion;
extern crate uom;
extern crate pandt;

use uom::si::length::centimeter;
use criterion::Criterion;
use pandt::types::{u32units, i64units};

fn simple_eq_u32(n: u32, n2: u32) -> bool { n == n2 }
fn simple_eq_i64(n: u64, n2: u64) -> bool { n == n2 }

fn uom_eq_u32(n: u32units::Length, n2: u32units::Length) -> bool { n == n2 }
fn uom_eq_i64(n: i64units::Length, n2: i64units::Length) -> bool { n == n2 }


fn simple_benchmark(c: &mut Criterion) {
  c.bench_function("simple u32", |b| b.iter(|| simple_eq_u32(100, 200)));
  c.bench_function("simple i64", |b| b.iter(|| simple_eq_i64(100, 200)));
}

fn uom_benchmark(c: &mut Criterion) {
  let u32n = u32units::Length::new::<centimeter>(100);
  let u32n2 = u32units::Length::new::<centimeter>(200);
  let i64n = i64units::Length::new::<centimeter>(100);
  let i64n2 = i64units::Length::new::<centimeter>(200);
  c.bench_function("uom u32", |b| b.iter(|| uom_eq_u32(u32n, u32n2)));
  c.bench_function("uom i64", |b| b.iter(|| uom_eq_i64(i64n, i64n2)));
}

criterion_group!(benches, simple_benchmark, uom_benchmark);
criterion_main!(benches);

By my reading of the code, this stems from the fact that Quantities that aren't from the same ISQ! are compatible with each other -- but at the cost of having to perform a conversion, apparently using num-rational. This is something I would be happy to forego - if uom's Eq implementation had a Rhs = Self, it could just perform a simple comparison. Perhaps a flag to the ISQ! macro could turn off this conversion generation and only support comparison of identical types?

Add support for integral types as the underlying storage type

Add support for integral types (i8, u8, i16, u16, i32, u32, i64, u64, usize? isize?, i128, u128) as the underlying storage type so that users can create new systems which do not use a floating point type. Use features to control code generation. Not all methods may be applicable. Types shouldn't be implemented for the ISQ.

Improve compile times

Compiling uom with all features enabled takes a significant amount of time. -Ztime-passes shows that the issue is in privacy checking:

$ RUSTFLAGS="-Ztime-passes" cargo +nightly test --no-run
...
time: 172.929; rss: 507MB	privacy checking
...

Timing for this pass increases linearly with the number of underlying storage types enabled. Enabling features for underlying storage types adds impl blocks and type aliases for existing types. No new types are added. Additionally, all structs and traits are pub in uom.

The graph below shows compile times, as reported by cargo for test --no-run in blue and build in red:
image

Test Build Features
15.74 5.5 f64
20.4 5.43 f32,f64
38.84 6.24 bigrational,f32,f64
37.75 7.4 rational64,bigrational,f32,f64
48.28 8.26 rational32,rational64,bigrational,f32,f64
55.52 9.26 rational,rational32,rational64,bigrational,f32,f64
67.57 10.89 biguint,rational,rational32,rational64,bigrational,f32,f64
96.81 12.5 bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
94.53 15.21 i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
106.92 16.4 i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
116.34 18.17 i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
131.29 20.2 i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
144.3 22.19 isize,i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
137.48 24.67 u64,isize,i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
158.4 26.93 u32,u64,isize,i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
194.9 29.69 u16,u32,u64,isize,i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
211.47 33.53 u8,u16,u32,u64,isize,i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64
198.5 36.63 usize,u8,u16,u32,u64,isize,i8,i16,i32,i64,bigint,biguint,rational,rational32,rational64,bigrational,f32,f64

Implement Display, LowerExp, UpperExp, ...

https://doc.rust-lang.org/core/fmt/trait.Binary.html
https://doc.rust-lang.org/core/fmt/trait.Display.html
https://doc.rust-lang.org/core/fmt/trait.LowerExp.html
https://doc.rust-lang.org/core/fmt/trait.LowerHex.html
https://doc.rust-lang.org/core/fmt/trait.Octal.html
https://doc.rust-lang.org/core/fmt/trait.UpperExp.html
https://doc.rust-lang.org/core/fmt/trait.UpperHex.html

Is there a way to use the fmt syntax or flags to include an implicit unit conversion, unit abbreviation, or unit label?

// What replaces `...` to allow the following outputs: 10 m, 10 meters, 32.8084 ft, 32.8084 feet
println!("{...}", new Length(10, meter));
  • Quantity
  • $quantities (Dimension)
  • BaseUnits
  • $unit (Measurement units)

Failure in tests::quantities_macro::float::f64::rem_assign

rem_assign arguments -99.32110293556526 and 4.277491538217234

---- tests::quantities_macro::float::f64::rem_assign stdout ----
	thread 'tests::quantities_macro::float::f64::rem_assign' panicked at '[quickcheck] TEST FAILED. Arguments: (A { v: -99.32110293556526 }, A { v: 4.277491538217234 })', C:\Users\appveyor\.cargo\registry\src\github.com-1ecc6299db9ec823\quickcheck-0.5.0\src\tester.rs:171:27
note: Run with `RUST_BACKTRACE=1` for a backtrace.

test failures with integers enabled

I didn't realize that I didn't have integers enabled when running tests until I tried to write integer-specific tests for the new Saturating support I'm working on. When I tried to run them, I got these failures (with master, with u32 enabled):

    si::acceleration::tests::u32::check_units
    si::area::tests::u32::check_units
    si::force::tests::u32::check_units
    si::frequency::tests::u32::check_units
    si::velocity::test::u32::check_units
    si::volume::tests::u32::check_units
    tests::system_macro::op_assign::u32::sub_assign
    tests::system_macro::u32::sub

It seems that travis isn't running tests with ints enabled either.

Create constructor method for unnamed quantities

Create constructor method for unnamed quantities:

/// https://en.wikipedia.org/wiki/Gravitational_constant
let G = Quantity::<ISQ<P3, N1, N2, Z0, Z0, Z0, Z0>, SI<f64>, f64>::new(6.674e-11);

Implement overflowing operations

  • overflowing_abs
  • overflowing_add
  • overflowing_div
  • overflowing_mul
  • overflowing_neg
  • overflowing_rem
  • overflowing_shl (?)
  • overflowing_shr (?)
  • overflowing_sub

Allow constants to be defined in system! or quantity! macro

Allow one location? Both?

Constant created by system macro (e.g. ISQ!) with the appropriate unit. Accessed in a submodule so there are no name collisions? let _ = uom::si::f32::velocity::c;

quantity! {
    quantity: Velocity; "velocity";
    dimension: ...;
    units { ... }
    constants {
        c: 299_792_458 meter_per_second,
    }
}

Constant created by system macro (e.g. ISQ!) with appropriate base units. let _ = uom::si::f32::G;

system! {
    quantities: ISQ {
        length: meter, L;
        ...
    }
    units: SI { ... }
    constants:{
        G: 6.674_083_1_E-11 ISQ<P3, N1, N2, Z0, Z0, Z0, Z0>,
    }
}

num dependency should have default-features=false

My application which uses uom doesn't need additional num-* crates. Given that num has optional dependencies which are enabled by default, I tried setting default-features = false in my Cargo.toml, but the default features were still enabled. I eventually figured out that this is because uom is depending on num without specifying default-features = false.

no_std doesn't seem to be transitive

This might just be me being new to rust, but I'm having a really tough time cross compiling uom for ARM. Starting from a lib crate that builds fine, if I add

[dependencies.uom]
default-features = false
features = [ "si", "usize" ]
version = "*"

to Cargo.toml and then run xargo build it tells me

error[E0463]: can't find crate for `std`
  |
  = note: the `thumbv7em-none-eabihf` target may not be installed

error: aborting due to previous error

error: Could not compile `num-traits`.

Caused by:
  process didn't exit successfully: `rustc --crate-name num_traits ~/.cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.0/src/lib.rs --crate-type lib --emit=dep-info,link -C codegen-units=1 -C debuginfo=2 --cfg feature="default" --cfg feature="std" -C metadata=459f1b7f38ae1057 -C extra-filename=-459f1b7f38ae1057 --out-dir target/thumbv7em-none-eabihf/debug/deps --target thumbv7em-none-eabihf -L dependency=target/thumbv7em-none-eabihf/debug/deps -L dependency=target/debug/deps --cap-lints allow -C link-arg=-Tlink.x -C linker=cortex-m-rt-ld -Z linker-flavor=ld -Z thinlto=no --sysroot ~/.xargo` (exit code: 101)

of special note there is the part that says --cfg feature="std". It seems that specifying no_std for uom isn't enforcing that constraint on upstream dependencies.

Add support for bytes?

Would support for 'bytes' be within scope for this library? Would that work out well with using f32 / f64 storage? It seems like perhaps using u64 for bytes, but floating point for all larger units might be useful?

non-deterministic failure in rem test

While I was working on an unrelated PR, I got this error during a test run:

---- tests::quantities_macro::fractional::f32::rem stdout ----
        thread 'tests::quantities_macro::fractional::f32::rem' panicked at '[quickcheck] TEST FAILED. Arguments: (A { v: 99.59967 }, A { v: 16.073349 })', C:\Users\radix\.cargo\registry\src\github.com-1ecc6299db9ec823\quickcheck-0.5.0\src\tester.rs:171:27
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    tests::quantities_macro::fractional::f32::rem

test result: FAILED. 147 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

Add tests for code generated by system! macro

  • Quantity<D, U, V>
    • Add
    • AddAssign
    • Sub
    • SubAssign
    • Mul
    • MulAssign
    • Div
    • DivAssign
    • Neg
    • Rem
    • RemAssign
  • Dimension
  • Units<D, V>
    • conversion
  • Unit
  • Conversion<V>
  • $quantities<$symbol>
    • Add
    • Sub
  • One
  • Debug
    • fmt
  • BaseUnits
    • Add
    • Sub
  • Units
  • $quantities!

Can't create "large" integer quantities when using smaller-than-default base units

I haven't had a chance to dig into this yet but I noticed a crash when trying to create reasonably large u64 Lengths.

This is some example code that fails at runtime when creating the Length quantity:

#[macro_use]
extern crate uom;
use uom::si::length::{centimeter, meter};
use uom::si;

mod u64units {
  ISQ!(
    uom::si,
    u64,
    (centimeter, gram, second, ampere, kelvin, mole, candela)
  );
}

fn main() {
  let max_u64 = u64::max_value();
  u64units::Length::new::<centimeter>(max_u64 / 16 + 1);
}

Here's my traceback:

thread 'main' panicked at 'attempt to multiply with overflow', C:\projects\rust\src\libcore\ops\arith.rs:309:45

   9: core::ops::arith::{{impl}}::mul
             at C:\projects\rust\src\libcore\ops\arith.rs:309
  10: num_rational::{{impl}}::div<u64>
             at ...\num-rational-0.1.40\src\lib.rs:442
  11: num_rational::{{impl}}::div<u64>
             at ...\num-rational-0.1.40\src\lib.rs:375
  12: uom::si::Quantity<Dimension, Units<u64>, u64>::new<Units<u64>,u64,uom::si::length::centimeter>
             at ...\uom-570397b139e674d8\595b3ae\src\quantity.rs:227
  13: uomtest::main
             at .\src\bin\uomtest.rs:39
  14: panic_unwind::__rust_maybe_catch_panic
             at C:\projects\rust\src\libpanic_unwind\lib.rs:99
  15: std::rt::lang_start
             at C:\projects\rust\src\libstd\rt.rs:52
  16: main

Using max_u64 / 16 instead of max_u64 / 16 + 1 works. This also seems to only happen when using lengths with a base unit smaller than meter -- if I use meter as my base unit I can create quantities up to u64::max_value().

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.