-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to support non-linear scale? #35
Comments
I've thought some on this (I had an impl somewhere). At first I thought we should have the ratio of two quantities whose units cancel to be a number but with things like this I think we might want to keep unit information. In general, I's like to help with this as time allows. I want this for C++23 and I want to use the implementation right now. I have electromagnetic field units lined up. Thank you for doing this. |
It seems like the simplest solution would be to create a new base unit and unit to represent this, and provide conversion functions in the library that internally discard the units to perform the calculations. Other than this limited loss of type safety, what is the drawback of this approach? |
Looking at not just "non-linear scale" for dB (which is reasonably niche), but also "offset linear", such as the transform between Celsius and Fahrenheit and Kelvin, which is more common. Firstly, just looking at where this is currently implemented: The basic logic currently performed is (obviously)
Where k is currently the The current template<typename To, typename CRatio, typename CRep, bool NumIsOne = false, bool DenIsOne = false>
struct quantity_cast_impl {
template<typename Q>
static constexpr To cast(const Q& q)
{
if constexpr (treat_as_floating_point<CRep>) {
return To(static_cast<To::rep>(static_cast<CRep>(q.count()) *
static_cast<CRep>(fpow10(CRatio::exp)) *
(static_cast<CRep>(CRatio::num) /
static_cast<CRep>(CRatio::den))));
} else {
return To(static_cast<To::rep>(static_cast<CRep>(q.count()) *
static_cast<CRep>(CRatio::num) *
static_cast<CRep>(CRatio::exp > 0 ? ipow10(CRatio::exp) : 1) /
(static_cast<CRep>(CRatio::den) *
static_cast<CRep>(CRatio::exp < 0 ? ipow10(-CRatio::exp) : 1))));
}
}
};
However, this logic is not very well abstracted, and some linear multiplication assumptions also exist in (at least) these related places:
One way to extend, is for the operation to become a generic "function transform" such that, eg:
ie, since we don't have a symbolic "solver", and unless we want to "bake in a special case" for linear and not support The "transform selection" for the simple template<Dimension FromD, Unit FromU, Dimension ToD, Unit ToU>
struct cast_ratio;
template<BaseDimension FromD, Unit FromU, BaseDimension ToD, Unit ToU>
struct cast_ratio<FromD, FromU, ToD, ToU> {
using type = ratio_divide<typename FromU::ratio, typename ToU::ratio>;
};
template<DerivedDimension FromD, Unit FromU, DerivedDimension ToD, Unit ToU>
requires same_unit_reference<FromU, ToU>::value
struct cast_ratio<FromD, FromU, ToD, ToU> {
using type = ratio_divide<typename FromU::ratio, typename ToU::ratio>;
};
template<DerivedDimension FromD, Unit FromU, DerivedDimension ToD, Unit ToU>
struct cast_ratio<FromD, FromU, ToD, ToU> {
using from_ratio = ratio_multiply<typename FromD::base_units_ratio, typename FromU::ratio>;
using to_ratio = ratio_multiply<typename ToD::base_units_ratio, typename ToU::ratio>;
using type = ratio_divide<from_ratio, to_ratio>;
}; So (at least) all the above extracts would need to change and be generalised to invoking these arbitrary transforms, and their inverses. This could be done by having a Note that I am not addressing any "change in dimension" due to the transform, as suggested with
it is dimensionless and it's therefore not clear where it belongs quite apart from it having a non-linear scale? Is this the general direction? |
I am not sure if temperature support should be discussed here as a scale. It is about affine types discussed in #1. Those types are not only about the zero offset but also about restricting some arithmetic operations on absolute values. Also, I do not think the way to solve affine types is to add more operations before and after multiplying by a factor in ratio (I tried and failed a long tie ago). I am pretty convinced that the only reasonable solution is to go with a
Please note that all of those special cases are mandated by the C++ standard right now in [time.duration.cast]. As we discuss affine types in another thread (#1) please scope here on logarithmic (and potentially other scales here) that most probably should be implemented in terms of |
Do we have an actual example of a logarithm (or other non-linear) scale of a quantity with unit? dB is not one in my opinion. It is the division of 2 quantities with the same dimension, resulting in a scalar without units and then 10* log Where does that fit as a dimensionless quantity? Are there other examples? Happy to take offset discussion to #1, to see if affine types actually provide a solution to these cases. |
There are a few in chemistry: pH, pOH, pKw, pKa, etc. Although I have never seen their values written with a unit, the only reason for that is that one absolutely MUST use concentrations in mol/l to calculate them. These quantities are not dimensionless by nature and using different units to express concentrations will result in different numbers. |
Yeah I am well familiar with the Chemistry ones. I would argue that they are not "units" in the conventional sense for many of the reasons you mention. They have many preconditions, you can't do conventional arithmetic them etc... I wonder if that is more generally true of such "units". If you think about what "log" does in the Taylor series expansion sense, you can see that "dimensions" in the traditional sense may not survive that process? |
I disagree. (Deci)bels only modify how a value of a certain dimension is measured, with the division simply being part of the calculation of the raw numeric value. "dB" is only shorthand when the reference value is implicit in literature; the more correct form is "dB re <constant> <unit>". The distinction is important in my line of work, where we deal with multiple incompatible decibels: dB re 1μPa, dB re 1mW, etc. Essentially, "dB re <constant> <unit>" should be convertible* to & from "<unit>" (or anything in that dimension) as they measure the same thing. * Theoretically, that is; numeric precision may limit the usefulness of certain conversions in C++ code I've been toying around with a design for a wrapper temlate that encodes the reference value & unit in the type, which would look something like this: template<
auto ReValue,
Unit ReUnit,
ratio R = ratio(1),
QuantityValue Rep = double
> class bel
{
// ...
};
// In this domain, SPL = 20 log10( p / 1μPa ) = 2 * dB re 1 μPa
using sound_pressure_level = bel<1, si::micropascal, ratio(2, 1, -1)>;
// Possibly in C++23?
using sound_pressure_level = bel<si::pressure<si::micropascal>(1), ratio(2, 1, -1)>; Hopefully I translated into |
Considering that #232 is open and #1 is solved, it seems that temperatures are not a solved problem. So I'm posting this here. At https://youtu.be/ABkxMSbejZI?t=4852, Peter Sommerlad touches this topic. |
ISO 80000-8 (acoustics) has logarithmic quantities. |
From #468 (comment):
|
Input from Timur Doumler:
Magnitudes are all over the place in observational astronomy: https://en.wikipedia.org/wiki/Apparent_magnitude |
Currently, it is unclear on how to handle a logarithmic (or other scales). There is only one library on the market that provides such a built-in support https://github.com/nholthaus/units
The text was updated successfully, but these errors were encountered: