Best practices with physical units

Hi,
I am working on an application that uses physical units. I think there are different approaches to this problem, which result in a tradeoff of convenience, saftey and maybe performance. What are your approaches and experencies to that?
I can think of:

let d_mm: f64; // 1. naming convention
type MM = f64; // 2. type alias
struct MM(f64); // 3. new type pattern for millimeter
struct Length(f64); // 4. new type for distance, similar to std Duration type

3 and 4 require extra crates (there are a lot of these) or implementations of methods like multiplication. I am also not sure, if I want to use crates like ndarray in the future. Conversions would be needed then, I think.

1 and 2 offer no protection against misuse, so I wouldn't recommend those.

Out of 3 and 4, 4 is easier to implement yourself, you only need to implement conversions between a normalized unit and the other units, as opposed to having to implement them for each pair of units. 4 is probably also simpler to use -- your code probably doesn't care if a certain length is in millimeters or inches, as long as it knows what length it represents (it is able to get the value in a unit it can work with).

The only advantage of 3 I can see is it might achieve a bit higher precision if there is no need to convert the value, but this difference might be negligible.

I would not expect there to be noticeable differences in performance.

But if there is a crate that fits your needs, I would recommend you use one -- I don't have experience with any of them, so I can't make a recommendation.

1 Like

both option 3 and 4 are fine to me, it depends on the use case.

for example, if the computation always use the same representation (e.g. to meet precision requirements) internally, and unit conversion only happens during data entry, then option 4 is more suitable, similar to Duration.

option 3 can be more convenient, e.g. when the application needs to preserve the units during the computation and conversions are usually lazily done to avoid undesired loss of precision.

1 Like

Thanks for your quick respones.
Regarding options 3 and 4, what type(s) would you use internally? Duration uses u32 and u64, which defends against loss of precision and is nice for comparisions. For physical computations like differences or divisions, i64 or f64 could be better suited.

Rust prides itself on zero cost abstractions. If implemented properly new types should simply provide an additional name space for compile-time guarantees without any performance issues.

I'm not sure about this extra crates note. Everything can be implemented directly:

struct Meters(pub f64);
struct Feet(pub f64);

impl Add<Feet> for Meters {
    type Output = Self;
    
    fn add(self, rhs: Feet) -> Self::Output {
        Meters(self.0 + 0.3048 * rhs.0)
    }
}

However, there is the uom crate, which could save writing a lot of that boilerplate stuff.

3 Likes

For almost all computations with physical units, f64 is the right default choice, because it avoids pre-defining the smallest representable difference. However, there are certainly cases where “fixed-point” use of integer types is much better, because it gives a consistent resolution over the whole numeric range — it’s just that those are necessarily more specific cases.

For example, Duration gets to use a fixed-point representation because it is specialized in representing lengths of time over which a computer program chooses its own scheduling — therefore, not shorter than a clock cycle. You cannot use Duration for analyzing the output of a particle accelerator. Another common example, though not physical units, is processing amounts of money — it is generally required for those to be computed to an exact specified precision, meaning that the proper representation is not “floating-point dollars” but “integer cents”.

The “best practice” here is not to pick one type but to consider your actual, more specific, requirements and the range of your input data.

3 Likes

I think, I will try Option 4 with floats first and see how it goes. Thanks to all.