I made a library for units of measure, because it seemed like a good way to learn comptime and also potentially useful to people.
It should enable unit-safe calculations without any runtime costs and I managed to combine some nice properties that I haven’t seen in similar libraries for other languages. In particular, you can apply scalar multiples and take arbitrary (comptime-known) integer powers and roots of a quantity without the types ever getting weirdly mismatched by floating-point error in the multiplier.
It’s only the second thing I have written in Zig, so I welcome any feedback,
Do you handle non-linear conversions, such as temperature (K to C or R to F), where there is not only a factor, but a quantity to add / subtract? If yes, may I ask how? These things are a PITA.
Excellent question. Yes, you can create a unit with an offset from a base unit and °C and °F are both predefined in terms of K in the library. You can do less with a quantity of a offset unit though. You can
compare them for equality, less than, etc,
convert them (to the non-offset unit or to another unit of the same dimension)
subtract two quantities of the same offset unit to get a quantity of the non-offset unit so: degreesCelsius(2.0).diff(degreesCelsius(1.0).eql(kelvins(1.0)) .
This is roughly analogous to subtracting two points to get an int.
add or subtract a quantity of the associated non-offset unit to a quantity of the offset unit (so K to °C) to get a new quantity of the offset unit: degreesCelsius(2.0).sub(kelvins(1.0).eql(degreesCelsius(1.0))
Following the analogy about this is like adding or subtracting an int to a pointer.
That’s it: convert to the non-offset version to do anything else.
My reasoning here was that I had set “arithmetic operations only do the float arithmetic” and “physically wrong calculations don’t compile” as requirements. Most arithmetic operations on (say) °C break one or both of those
If you want to distinguish further from unit testing, including “SI” might be a good idea. The core idea of units of measure actually is the quantity being measured AFAIUI, as such one could base the name on that, as well. Personally, I think this is a fine name. Speaking of quantities, I wish you put them more upfront in your implementation. The real question of compatibility in arithmetic and comparisons really is in quantities, not their dimensionality. Like, what CAN you compare? → dimensionality what can you compare without “cast” ? → quantities. The special case of the dimensionless “unit” of counting critically is dependent on its quantity. You introduce a unit “iguana” i your example, which really ought to be a quantity AFAIUI. Also I don’t think it’s a good idea to be able to introduce “base units”, as they imply dimensionality.
While it is dimensionally sane to compare the width of a desk with the height of a stool, there exist a whole set of associations with each quantity that make them not compatible by default. Other examples include, e.g., the unit “newton meters” which may involve a (canceled) circular component, e.g. torque vs. work (Joules equals newton meters)
One document that clearly presents this idea in very easy read IMO is NIST Special Publication 330 (Special Publication 330). If you haven’t read this english adaption of the SI yet, maybe have a look at the subtleties of differences amongst it (and thus what physicists would expect) and your implementation so far. I think it would be worthwhile at least documenting, if not eliminating, deviations between those two idea worlds.
Thanks for the recommended reading. and the feedback
I can see benefits to making the “quantity” types more prominent compared to the “unit” types but there are maybe some wrinkles in terms of what Zig users expect with peer-type resolution for the float part. I tried to copy the behaviour from std.math.complexon that so you can compare and add quantities of different types as long as it is only the float type that is different. The name I current have for the “non-float” bit oif the quantity is Unit; I don’t know if there is something better.
It will take some thought to work out how to stop quantities that aren’t physically comparable being compared when the dimensions cancel without shutting down the cases that are useful. I think the case of torque and work is kind of dealt by Radian being supplied as a unit type distinct from One. This diverges from the current SI, but follows every similar library I have seen in engineering firms. To my mind, torque is really in joules per radian.
Maybe I am at cross purposes here. I would see “time” as a dimension, a specific period of time as a quantity of time and “float * unit” as the way we represent the quantity (and therefore what we call “quantity” in code).
I definitely don’t want to lose extensibility by having enums for dimensions and units.
I believe the reason for that being that they do dimensional and unit analysis, not quantity analysis. The radian really is dimensionless. So you’d have to compare units again instead of quantities, which is the wrong thing to look at.
I’m aware of a few implementations, I dabbled in this pool some 15 years ago as well (wrote a scala compiler plugin, never finished it beyond my thesis about said matter which I happened to write at NIST for the UnitsML group, ahem - I’m saying that not to give gravitas to what I’m saying, but to signify that I’ve professionally spent some time thinking through this mess and thus have more than just a random set of opinions from a user perspective) and all the implementations I remember only ever did dimensions-at-compile-time and units-at-runtime, leaving out the interesting category theory part of actually supporting quantities. So I absolutely can see where you’re coming from, all the prior art has the same way of thinking. Now the question you can ask and answer yourself is whether or not that’s a local maximum
I recently watched a talk about a C++ library mp-units. You can find a link to this talk in the Video Introduction readme section. The library may be included in the C++29 standard.
I haven’t investigated it thoroughly, but looks like it tries hard to disambiguate any usage of units and make it correct. Quantities have kinds and dimensions. Quantities of the same kind have the same dimension, but quentities of the same dimension may belong to different kinds. Quantities of dimension one (like radians) are also considered. Also, quantities may be scalars or arbitrary tensors.
It looks like a complex topic and I personally need more time to research it. So, I can’t comment how good it actually is.