Replies: 12 comments 94 replies
-
specify angles in terms of a concept, and then you can write different angle types for the different purposes. |
Beta Was this translation helpful? Give feedback.
-
What I mean is give up on the idea of making angle a quantity in mpunits type, using the compile time conversion factor. It really doesnt work vry well. |
Beta Was this translation helpful? Give feedback.
-
Here are 3 problem to solve.
The 2 types don't have to be the same type and they dont need to have anything to do with mp_units types, nor to have any other functionality so should be quite quick to write I have solved part2 with my venerable quan library using both types of angle, thus also showing a solution to part1. I didnt attempt part 3 int main()
{
// number of encoder bits representing one revolution
constexpr int encoder_bits_per_rev = 2100;
// create a fraction of revolution angle type for incrementing by exactly one bit at a time
using encoder_angle_type = quan::fraction_of_revolution<
quan::meta::rational<1>,
quan::meta::rational<encoder_bits_per_rev,1>,
int
> ;
// encoder angle representing one bit change
constexpr encoder_angle_type encoder_incr{1};
// current_angle_rad attempts to hold the current position using the standard angle unit , the radian
quan::angle::rad current_angle_rad; // so called mathematic angle
// current_angle_fract attempts to hold the current position using the bespoke angle unit , encoder_angle_type
encoder_angle_type current_angle_fract; // so called fraction of revolution
for ( int i = 0; i < encoder_bits_per_rev; ++i){
current_angle_rad += encoder_incr;
current_angle_fract += encoder_incr;
}
std::cout.precision(20);
std::cout << "current pos in rad = " << current_angle_rad / (2* quan::angle::pi) <<'\n';
std::cout << "current pos in rev = " << current_angle_fract.numeric_value()/ encoder_bits_per_rev <<'\n';
} The output on my pc is current pos in rad = 0.99999999999998467892 Clearly the best choice for this particular task is the custom fraction of revolution type. So now to solve the problem using your angle type(s). |
Beta Was this translation helpful? Give feedback.
-
And what if we redefine struct ratio {
std::intmax_t num;
std::intmax_t den;
std::intmax_t exp;
std::intmax_t pi_exp = 0; // <-----
// ...
}; It addresses only Alternatively, we could make |
Beta Was this translation helpful? Give feedback.
-
It can be enforced with dedicated |
Beta Was this translation helpful? Give feedback.
-
Overloads of trigonometric functions taking However, when using
I wonder if one extra multiplication/division operation will make any difference when trigonometric functions are involved. |
Beta Was this translation helpful? Give feedback.
-
Just a few ideas I've been kicking around: Using a
|
Beta Was this translation helpful? Give feedback.
-
So if I take i.e. |
Beta Was this translation helpful? Give feedback.
-
If I read the comment by @mpusz above correctly, a design goal is to provide "exact" units computation, so that we can retain human-readable units through computations. Here, with "exact unit computation" I mean that no approximations shall enter the units represented by the type-system, not necessarily the runtime values. I find that a reasonable goal. Therefore, I believe the only solution is to treat those irrational factors symbolically. The simplest one is definitely a I would favour a system that allows products of rational exponents of arbitrary symbolic constants to be retained. This would enable exact and free computation with frequent constants in domain-specific cases, such as the speed of light or other physical constants when working in natural units. Technically, that will require a system quite similar to the current unit system, which also enables products of exponents of arbitrary symbolic base units. This is why a separate dimension for angles appears like a solution. However, I think that is misleading. Separating dimensions (or equivalently base units) is something we do because it helps avoiding wrong computations by making it less likely that accidentally two "incompatible" quantities happen to have the same unit. Separating symbolic constants however we do to prevent numerical representation issues. That is, the rules when and how conversions that apply pre-factors should be done (both automatic/implicit and explicit) are significantly different between those two cases. In fact a separate dimension/base-units for angles does not solve the pi issue on it's own, unless there are two separate base units, one including the pi constant, the other not. Instead, the separation of angles into its own base dimension tries to solve a different goal: To prevent accidental mixing of dimensionless quantities of the "angle" kind with dimensionless quantities of other kinds (such as "ratio"). However, in some cases, those distinction becomes blurry and makes computation awkward, e.g. when working with trigonometric functions decomposed into exponential functions. Therefore, I believe where to put that distinction is an application specific compromise in the same way that certain domains prefer to work with natural units, where essentially all dimensions are equivalent (less protection, more convenience), while others prefer to work in SI units (more protection, less convenience). |
Beta Was this translation helpful? Give feedback.
-
FYI: I plan to work to address all the issues discussed soon. The only problem is that I have too many things on my plate lately, but I hope to have time for it in March. |
Beta Was this translation helpful? Give feedback.
-
Hi! I'm a SWE at Aurora Innovation, and a units library enthusiast. 🙂 I'd like to share some details about how we've solved this problem in our units library, both in the hope that it's useful (a A little while after we landed the library, I found this comment from @burnpanck, which came the closest to our solution of anything I've seen anywhere. The main similarities are:
The main differences are:
Here's a basic example (adapted slightly for this format): // Convenient shorthand, to aid in understanding what follows:
template <std::intmax_t N = 1>
using Pi = BasePower<ConstPi, N>;
// An example of defining a relatively scaled unit in our library.
using Degrees = scale_unit<Radians, product_t<Scale<Pi<>>, ratio_t<1, 180>>>; The relative scale factor for this unit would expand out to something like the following: Scale<BasePower<Int<2>, -2>, BasePower<Int<3>, -2>, BasePower<ConstPi, 1>, BasePower<Int<5>, -1>> (Yes, the exponents are integers for now. While we plan to support rational exponents, we haven't found the need yet; the library is pretty new.) Notice that the basis factors (i.e., prime numbers, and irrational constants) are naturally sortable (because irrationals are an ordered field extension of the rationals), which gives them an unambiguous canonical ordering. Or, more directly: Pi goes after 3 and before 5. This canonicalization is necessary to keep the Scale factors (i.e., positive real numbers) 1:1 with the types. Two other things that are important for this canonicalization:
One natural worry with this approach would be the compile time cost of the variadic pack. So, we measured. We created "adversarial" units with over 20 different basis factors, and made repeated measurements on some units-heavy code. We did observe a slowdown, but it wasn't subjectively noticeable; and, it was basically equivalent to running the nholthaus library on the same code, but with almost all of its units stripped out. So, in practice, performance really does not seem to be a concern. Another worry is that the types can get pretty long. It's true that representing 180 by its prime factorization is a little counter-intuitive for the reader of the compiler errors. 🙂 That said, the excellent downcasting facility should completely hide the scale factors from the end user for every named unit. One nice strength is the separation between the symbolic computation, and the actual conversion process. Every conversion is based on a single Those are the basic ideas of our solution for Scale factors. If you adopted something like this, you could immediately support Degrees, and also remove the custom solution you have for powers of 10 beyond the reach of |
Beta Was this translation helpful? Give feedback.
-
For posterity's sake, and discoverability, my understanding is that #300 will definitively resolve this discussion. If anyone disagrees, or if I've missed anything, feel free to chime in here. Otherwise, consider upvoting this comment for visibility. |
Beta Was this translation helpful? Give feedback.
-
Introduction
Currently,
units
handles[1] conversion between quantities using a conversion[2]units::ratio
[3] which is a rational number with some exponent. This works for numerators and denominators of which fit into anstd::intmax_t
, but irrational constants such as π would have to be truncated, resulting in a loss of precision during conversion.Proposed solutions
During the discussion of how to handle planar angle units[4], several solutions were proposed for how to deal with the issue of conversion[5,6]. These are summarized as follows:
In option 2, treating π=1 or π symbolically are somewhat similar, although I think the former is simpler to use and implement, and there is no real advantage of carrying π symbolically through the calculation since any exponents will already be carried through via the angle base dimension. Thus, we will consider only the former option π=1 here. Option 3 will be split into fixed and arbitrary precision options for discussion. Option 4, like option 3, requires coming up with an abstraction/concept for the unit conversion which generalizes
units::ratio
and allows the user to specify how the conversion should be done. Then the advantages and disadvantages of each option are as follows:units::ratio
conversion infrastructureunits::ratio
conversion infrastructureRep
, there will be external precision loss during conversion to/from theunits
quantity. This will be controlled by the user, though.units
for angles altogether.units::ratio
conversion factor to be afloat
,double
, orlong double
units
designI've probably missed some things here, but this can serve as a starting point for the discussion. For what it's worth, it looks like
Boost.Units
uses option 3.i withdouble
precision[11].Currently, I'm kind of inclined to say that option 4 (creation of a conversion abstraction/concept) might be the best option since we essentially allow the users to decide what their precision needs are when dealing with irrational conversion factors. This is kind of like the
Rep
concept which is general enough to allow e.g. integer, floating point, arbitrary precision, or periodic numerical representations, depending on what the user requires. The conversion however is not entirely independent from theRep
concept, so I'm not entirely sure what this conversion abstraction would look like and how it might depend onRep
.References
[1] https://github.com/mpusz/units/blob/6d7cda949eee5c8d0428cbc16b685c497d92534f/src/include/units/quantity_cast.h#L107
[2] https://github.com/mpusz/units/blob/6d7cda949eee5c8d0428cbc16b685c497d92534f/src/include/units/quantity_cast.h#L59
[3] https://github.com/mpusz/units/blob/1ef2bf9f28020f29cb86a46548f4f41daf7518a6/src/include/units/ratio.h#L49
[4] #99
[5] #99 (comment)
[6] #99 (comment)
[7] https://oeis.org/A002485
[8] https://oeis.org/A002486
[9] http://qin.laya.com/tech_projects_approxpi.html
[10] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2268r0.html#floating_point
[11] https://github.com/boostorg/units/blob/21c9dcaf3a334692cf043ac70f86857a96431d4b/include/boost/units/base_units/angle/degree.hpp#L17
Beta Was this translation helpful? Give feedback.
All reactions