Closed nebkat closed 10 months ago
(Apologies if I am too early reporting issues on 2.0 branch!)
No need to apologize 😄 The V2 is already on the mainline, so it should be steady and robust.
Yes, I agree this feature is tricky, and this design decision resulted from the fact that value_cast<si::radian>(5 * angular::revolution)
returned an integral representation type that was off a lot from the expected value. This is why I disallowed it in the first approach and was waiting for the feedback from the community 😉 Thank you very much for bringing this up!
Now, we have a design question to solve. If the conversion between two units has an irrational factor and the quantity has an integral representation type, what should we do when we do value_cast<U>
to change the unit (note that the quantity.in(U)
will not compile as the operation is not value-preserving):
int * long double
)value_cast<long double>(q)
will be needed to change the representation type).Please note that we have a similar problem in trigonometric functions:
For now, we require a floating-point representation there as well, even though the user could argue that an angle in degrees makes a lot of sense. How should we handle this as well?
disallow such a cast at all (before that a value_cast
(q) will be needed to change the representation type).
For me this would be the most sensible option:
value_cast<si::radian>(5 * angular::revolution)
the fix is of course easy - replace 5
with 5.0
.auto
defs 😀 ).
double
is a good default representation, but shouldn't be assumed from the unit if unspecified (unlike a normal int * double = double
scenario where it is specified).value_cast<si::radian, long double>
could be introduced?
value_cast<km, uint8_t>(uint16_t { 12345 } * m)
.quantity.in<Rep, U>()
, which is already protected by QuantityConvertibleTo
for int to float conversions.For now, we require a floating-point representation there as well, even though the user could argue that an angle in degrees makes a lot of sense. How should we handle this as well?
The std::sin()
mapping is:
float -> float
double -> double
Integer -> double
So I think an implicit cast to std::conditional_t<std::is_floating_point_v<Rep>, Rep, double>
would work:
template<ReferenceOf<angle> auto R, typename Rep, typename Rep2 = std::conditional_t<std::is_floating_point_v<Rep>, Rep, double>>
[[nodiscard]] inline quantity<one, Rep2> sin(const quantity<R, Rep>& q) noexcept
requires requires { sin(q.value()); } || requires { std::sin(q.value()); }
{
using std::sin;
return make_quantity<one>(static_cast<Rep2>(sin(q.in<Rep2>(radian).value())));
}
The
std::sin()
mapping is:
This is a nice find.
If std::sin
already does that,
or the other constraint is satisfied (requires { sin(q.value()); }
),
then there's no need for the constraint requires treat_as_floating_point<Rep>
.
This last constraint should be dropped
and the return type not fixed to use Rep
(i.e., quantity<one, Rep>
),
but whatever sin
returns.
disallow such a cast at all (before that a value_cast(q) will be needed to change the representation type).
For me this would be the most sensible option: If we return integral, there can be huge loss of precision as you mentioned, so no good.
I am not sure if this exception is much different from the value_cast<ms>(2 * s)
case. I think that irrational factors and rational ones with denominator != 1 are similar scenarios in this regard. In both cases, we risk losing precision and that is why we actually introduced value_cast
. So maybe we should allow such cast and return the type as provided by the user in the input argument?
The std::sin() mapping is: Integer -> double
This is true. However, std::sin()
always takes a value in radians, and we allow any unit here (i.e. degrees) and we need to convert to radian
before calling std::sin()
. We can do something like the below but I am not sure if it also is not surprising:
template<ReferenceOf<angular_measure> auto R, typename Rep>
[[nodiscard]] inline QuantityOf<kind_of<dimensionless>> auto sin(const quantity<R, Rep>& q) noexcept
requires requires { sin(q.value()); } || requires { std::sin(q.value()); }
{
using std::sin;
if constexpr (!treat_as_floating_point<Rep>)
return make_quantity<one>(sin(value_cast<double>(q).value_in(si::radian)));
else
return make_quantity<one>(sin(q.value_in(si::radian)));
}
And what about asin()
. std::asin()
also supports integral types but the only possible values here are -1
, 0
, and 1
. Again we allow any unit here (i.e. percent
) and conversion to one
could also be truncating.
Very amusing... it would appear that GitHub sees @mpusz as a proper subset of @JohelEGP? :laughing:
Now:
(Apologies if I am too early reporting issues on 2.0 branch!)
RepSafeConstructibleFrom
prevents quantities from being constructed with integral representations if the desired unit has an irrational magnitude relative to its base unit (since 05ed7dae88e6c0b3201e86585738664742c68879).For example,
angular::revolution
is defined asmag<2> * mag_pi * radian
, leading to the following:Example
Seems to me like
make_quantity()
should never be constrained in this way, but rather only in a conversion when the relative magnitude between two units is irrational - and this is currently handled correctly byQuantityConvertibleTo
.