Skip to content
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

implement value_cast<ToQ> and value_cast<ToQP> #571

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

burnpanck
Copy link
Contributor

Fixes #568

@mpusz mpusz marked this pull request as draft May 10, 2024 20:08
@mpusz
Copy link
Owner

mpusz commented May 10, 2024

BTW, instead of [WIP] in the title you can set PRs as "draft" until they are ready. I just did it for this PR.

@burnpanck burnpanck marked this pull request as ready for review May 12, 2024 09:22
@burnpanck burnpanck changed the title [WIP] implement value_cast<ToQ> and value_cast<ToQP> implement value_cast<ToQ> and value_cast<ToQP> May 12, 2024
@burnpanck
Copy link
Contributor Author

burnpanck commented May 12, 2024

Given that it is somewhat nontrivial to implement a correct general value_cast<ToQP>(qp) which allows adjustments in the point_origin, I believe there is value in having this in the library. The challenge is that neither FromQP nor ToQP may have enough range to reach each other's point_origin. I believe the provided implementation manages to achieve a result within std::max(FromQP::epsilon,ToQP::epsilon) of the exact conversion result, assuming qp is within the range of ToQP. Otherwise, the conversion is unspecified (any may well be undefined behaviour, e.g. if the representation is a signed integer).

@burnpanck
Copy link
Contributor Author

Oh, shall I try to sqash?

Comment on lines +190 to +191
* using ToQP = quantity_point<mm, B, int>;
* auto qp = value_cast<ToQP>(quantity_point{1.23 * m});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if the value_cast<Q>(qp) is really needed? Anyway, this example needs to be fixed ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, this example needs to be fixed

Right, indeed obvious copy-paste error.

I am not sure if the value_cast<Q>(qp) is really needed?

value_cast<Q>(qp) operations are convenient as part of implementations of value_cast<QP>(qp)-like operations (see below). While my implementation of the latter tries to select the best possible implementation based on the provided types, an expert user may know more about the value range that is in fact being used in their case and instead want to implement a different order of operations. Then, they would also like to be able to use value_cast<Q>(qp). Finally, it provides some symmetry to value_cast<Q>(q). On the other hand, one could argue that value_cast<Q>(qp) misleadingly suggests that the result type is going to be Q, which of course it is not. However, that same argument could be made for the value_cast<Repr>(...) overloads. Anyway, I don't have all too strong feelings here, we can also drop it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it is needed and it may be misleading indeed.

value_cast<Q>(q) makes sense as someone may have a typedef for quantity and does not want to extract a unit and rep all the time for a cast. The same applies to quantity_point.

On the other hand, value_cast<Q>(qp) is strange, as one should not have a quantity typedef for an abstraction modeled through a quantity_point.

Comment on lines +252 to +271
constexpr Magnitude auto c_mag = get_canonical_unit(qp_type::unit).mag / get_canonical_unit(ToQP::unit).mag;
constexpr Magnitude auto num = detail::numerator(c_mag);
constexpr Magnitude auto den = detail::denominator(c_mag);
constexpr Magnitude auto irr = c_mag * (den / num);
using c_rep_type = detail::maybe_common_type<typename ToQP::rep, typename qp_type::rep>;
using c_mag_type = detail::common_magnitude_type<c_mag>;
using multiplier_type = conditional<
treat_as_floating_point<c_rep_type>,
// ensure that the multiplier is also floating-point
conditional<std::is_arithmetic_v<value_type_t<c_rep_type>>,
// reuse user's type if possible
std::common_type_t<c_mag_type, value_type_t<c_rep_type>>, std::common_type_t<c_mag_type, double>>,
c_mag_type>;
constexpr auto val = [](Magnitude auto m) { return get_value<multiplier_type>(m); };
if constexpr (val(num) * val(irr) > val(den)) {
// original unit had a larger unit magnitude; if we first convert to the common representation but retain the
// unit, we obtain the largest possible range while not causing truncation of fractional values. This is optimal
// for the offset computation.
return value_cast<typename ToQP::quantity_type>(
value_cast<c_rep_type>(std::forward<QP>(qp)).point_for(ToQP::point_origin));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reuse sude_cast here? I wouldn't like to duplicate the implementation. Maybe sudo_cast could benefit from new logic as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sudo_cast is being used (indirectly), through the two value_cast. The apparent duplication is just in order to determine the conversion factor to be applied to the numeric value. The runtime code in the body here does not apply that conversion factor at all; but it needs to know that factor is eventually going to make number larger or smaller. That is needed in turn to decide when to do that conversion: if the number gets larger, we want to postpone until after the offset adjustment, and if it gets smaller, we want to do it before the offset adjustment.

We could probably create a second overload of sudo_cast which operates on quantity_point (basically the content of this value_cast, minus the constraint on matching quantity_specs, and ensuring that the quantity_spec is being casted correctly). Then, we could extract that calculation of the rescaling factor into a helper function and use it from both overloads.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that. I've already learned that sudo_cast is really fragile, and any changes should be verified against the codegen in Compiler Explorer. I would not like to have nearly copy pasted implementation in a separate file because it would be hard to maintain and keep consistent.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@burnpanck, I hope that you successfully delivered the upgraded project on time. Do you still work on this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactor is done, but we had to postpone a few features, which we are still delivering as we speak. I do intend to finish this one tough, as our code is already heavily reliant on these value_cast variants.

Copy link
Owner

@mpusz mpusz May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@burnpanck, I plan to release mp-units 2.2 in the following days. Please let me know if you will have time to finish it soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

I miss value_cast<ToQ> and potentially value_cast<ToQP>
2 participants