units icon indicating copy to clipboard operation
units copied to clipboard

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

Open burnpanck opened this issue 4 years ago • 5 comments

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:

  • Absolute units:
    • do not support arithmetic operations except addition and subtraction of a relative unit
    • convert with application of the linear offset
  • Relative units:
    • do support all common arithmetic operations
    • convert without application of the linear offset

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.

  • Addition of relative units are consistent, such as 5 r°C + 9 r°F: Convert first to Fahrenheit, and you'll find 9 r°F + 9 r°F = 18 r°F. Convert first to Celsius and you'll find 5 r°C + 5 r°C = 10 r°C. Both results are consistent, because 18 r°F = 10 r°C.
  • Addition of absolute units do not make sense: What should 5 a°C + 9 a°F even mean? Convert first to Fahrenheit: 41 a°F + 9 a°F = 50 a°F. Convert first to Celsius: 5 a°C + -4.8 a°C = 0.2 a°C. But 50 a°F = 10 a°C, not 0.2 a°C! Even the most naive interpretation of either 14 a°F or 14 a°C would all be inconsistent.
  • Multiplication (e.g. squaring) of relative units is constistent: (10 r°C)² = 100 r°C². Converting this expression to Kelvin, you'll find (10 rK)² = 100 rK².
  • On the other hand, if the squared absolute units a°C² and aK² existed you would find (10 a°C)² = 100 a°C². But since 10 a°C ~ 293 aK, you would also find (293 aK)² = 85849 aK². Whatever conversion function (linear offset or otherwise) your would define to convert 85849 aK² to 100 a°C², you would find it to be inconsistent if you repeat the same for other temperatures.

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).

burnpanck avatar Oct 24 '19 09:10 burnpanck

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?

JaapAap avatar Oct 24 '19 10:10 JaapAap

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.)

eflyon avatar Oct 25 '19 15:10 eflyon

@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 avatar Oct 03 '20 15:10 nholthaus

@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.

nholthaus avatar Oct 03 '20 15:10 nholthaus

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.

chiphogg avatar Jan 09 '23 16:01 chiphogg