nholthaus / units

a compile-time, header-only, dimensional analysis and unit conversion library built on c++14 with no dependencies.
http://nholthaus.github.io/units/
MIT License
938 stars 134 forks source link

Units with linear offsets are fundamentally broken (e.g. temperature units) #240

Open burnpanck opened 4 years ago

burnpanck commented 4 years ago

The suggested solution to (#75) as implemented is no solution at all. As others have pointed out (e.g. in a comment by @JaapAap), relative and absolute are really not compatible - you cannot have both functionalities in one arithmetic type:

A library will have to decide which of the two it wants to support (e.g. boost supports both through different types), but mixing those two behaviors leads to plain wrong (inconsistent, non-physical and confusing) behavior. This seems a confusing topic, given that I seem to be having this discussion in every other units library of the programming languages I'm working with. The fundamental benefit of working with units is that it does not matter in which unit operations are performed, i.e. one may first convert, then add, or first add, then convert units. This makes absolute units fundamentally incompatible with most arithmetic operations.

Let me show this using an example, where I prepend r to unit names to indicate relative units, and a to indicate absolute units.

Because most arithmetic operations make no sense when applied to units that have linear offsets in their conversion rules, I wager to say that most people used this package mostly for conversion when working with temperatures. Otherwise, more issues would have popped up (#75 is one of those - that user really expects relative unit behavior). Therefore, current users implicitly assume absolute unit behavior. The only sane solution without breaking people's expectations is thus to disallow all arithmetic operations on temperatures.

However, since we seem to be at an API-breaking version change (version 3.x upcoming), maybe now would the time be to reconsider? I would consider the library much more useful, if temperature units would support arithmetic too, i.e. represent relative units and do not apply any linear offsets at all. That kind of functionality would be better implemented using an absolute_unit<dimension,...> type such as boost did, because it makes it easy to prevent arithmetic operations (and provide good error messages).

JaapAap commented 4 years ago

Thanks for that excellent overview! Obviously, I can only agree. Note that in my work I would need both absolute and relative temperature - some of the models I use expect absolute temperature as their input, while others expect temperature differences/changes, and would therefore need relative temperature. To me it is clear that both have their uses, so it would be great of they would both exist. Open question: would this only relate to temperature, or are there other quantities that exhibit this issue as well?

eflyon commented 4 years ago

Open question: would this only relate to temperature, or are there other quantities that exhibit this issue as well?

I work with some legacy code that has 6 different rotation measurement units (with zero type safety, as they're typedefs):

  1. bearing rads: referenced from east, positive means counterclockwise
  2. bearing degrees: referenced from north, positive means clockwise
  3. depression rads: referenced from horizontal, positive means down
  4. depression degrees, referenced from horizontal, positive means down
  5. angle rads: difference in either sort of rads
  6. angle degrees: difference in either sort of degrees

1 & 2 differ more than the units alone suggest, and they seem close to "absolute" units in the definition given above.

(As a separate issue, the pairs 1 & 3 and 2 & 4 share the same unit, but cannot be meaningfully converted. This may be beyond the scope of the library, though as long as I can add separate dimensions for horizontal and vertical rotation, it looks workable.)

nholthaus commented 3 years ago

@burnpanck you're correct, and it certainly has been confusing for me. As you probably deduced, a Celsius -> Fahrenheit temperature conversion was pretty much the limit of what I imagined and tested.

I would like to see this corrected in 3.0, which I'm trying to roll out early Q1 of 2021. It might not be that difficult to implement with 3.0's arbitrary dimension system... instead of having a 'temperature' dimension, we'd have one for each: relative and absolute. As you suggested, it probably makes sense to even have polymorphic bases for absolute and relative dimensions to make disallowing arithmetic easier and to extend the concept.

Would you be willing to experiment with it a bit, and perhaps put forward a PR?

nholthaus commented 3 years ago

@eflyon That was a very confusing lesson I learned when putting v2.x of the library together. I went with 'angle' as a dimension (which is also pretty controversial).

Version 3 allows arbitrary, user-defined dimensions, with the express purpose of allow unit "domains" like what you describe. If you're willing to experiment with v3 and defining some different angles, I'd be very interested in pulling in the results for the 3.0 debut release.

chiphogg commented 1 year ago

The simplest and most robust answer is that the "absoluteness" or "relativeness" attaches not to the unit, but to the type. In other words, you need a separate notion of a "quantity" type and a "quantity point" type.

You can have a quantity of celsius that trivially converts with a quantity of kelvins, in both directions. With quantity points, we'd pick up the offset when we do the conversion.

Going a level deeper: if the library supported integer storage types, we'd be able to convert between quantities of kelvins and celsius quite freely, but converting between quantity points would fail to compile. However, converting between quantity points of millicelsius and millikelvins would work, even with integer storage types.

See this article for more background on the concept underlying "quantity point": Affine Space Types.