-
Notifications
You must be signed in to change notification settings - Fork 86
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
Modulo operations #448
Comments
The result type of 1-3 should be the same as
As for the rest, considering #412, I'm not sure if it makes sense to support all of them. But if they are to be supported, they should behave like the built-in operator (https://eel.is/c++draft/expr.mul#4). Let |
If we are talking about |
Thanks, taking into account https://eel.is/c++draft/expr.mul#4, I think that we need to support both versions (like std::chrono::duration does) as both of them are correct in value and type for this equation:
|
What about the unit of the dimensionless divisor. Should it be constrained to have a unit of:
|
I just realized that the below provided by @JohelEGP are wrong:
Again to satisfy https://eel.is/c++draft/expr.mul#4:
Does it have any sense? 😕 |
Aren't they converted to the common unit before the operation? Just like |
Multiplication and division never converted to a common unit. The ratio (sometimes really huge) is stored in a type. Also, what is a common unit for |
I shouldn't have referred to |
Exactly, but the equation https://eel.is/c++draft/expr.mul#4 should be preserved, right? |
Where do we stand with the original examples? |
We can implement anything 😉 Modulo arithmetic, as you described in #448 (comment), makes sense but is inconsistent with the division, which means https://eel.is/c++draft/expr.mul#4 will not hold. #448 (comment) is consistent with division and will make https://eel.is/c++draft/expr.mul#4 happy, but such results may be really confusing to the users. |
This is what we have on the mainline compared to chrono: https://godbolt.org/z/7q9o8dWr4. |
I also tried on Python's Pint:
They are doing the same as we do (but decay to floating-point). |
We could consider using a common unit if we are dealing with the division of the same quantities and integer types, but it would :
|
Any ideas on how to proceed? |
Right. mp_units'
https://godbolt.org/z/66Gvcs954
For starters, there seems to be something wrong with |
Yes, I noticed that and started to investigate. And the more I thought about that, the more I was confused about what is the expected behavior. Let's agree on some reasonable behavior and I will implement it. |
It seems you applied #231 (comment), which goes against:
Although |
Right, but it would be good to have a consistent behavior between division and its reminder for integral types. |
Right now, it's wrong: https://godbolt.org/z/66Gvcs954.
They should all output |
So, should we implement modulo in terms of the common unit and ignore the fact that the division will work otherwise? |
Really interesting question! First, I can say what Au does. We support I had never actually considered a case such as All of which is to say: I now think that I accidentally made the right choice in forbidding this operation. 😁 I think the main worry with this approach is that it's inconsistent with 7.6.5. However, I think this is fine. IMO, that part of the standard only applies to raw numeric types. Quantities are different in a variety of ways, and break all kinds of otherwise reasonable assumptions (e.g., Another worry is that the defining relationship of division won't hold exactly. That relationship is: (n / d) * d + (n % d) == n It will produce the correct quantity --- so, the I think it would be nice if it reproduced the units as well, but I don't see a pathway to get there with simple rules, and complicated rules would really worry me. So I think this is actually fine as well. I guess I shouldn't neglect the original quiz. 🙂
|
I just cooked up this test (in Au). It passes! 😁 TEST(Modulo, ReturnsResultInCommonUnits) {
EXPECT_THAT(hours(5) % minutes(120), SameTypeAndValue(minutes(60)));
EXPECT_THAT(minutes(300) % hours(2), SameTypeAndValue(minutes(60)));
EXPECT_THAT(minutes(300) % minutes(120), SameTypeAndValue(minutes(60)));
} |
I agree with this. It should be sufficient that it holds when |
I am not sure how it should work if the below are true?
|
Seems it's because it returns a quantity of number |
The library never brought two units into a common unit before dividing. In general, for quantities of different dimensions it is impossible to do so. The same is done in Pint and JSR 385 (Java library). See the list of drawbacks here: #448 (comment). |
Also, see the #231 discusion. |
I think that's what it effectively did in 0.6.0: https://godbolt.org/z/c5hjo1hf8.
|
Yes, in 0.6.0 the modulo worked as we agreed above, but the division was always truncating the value and never used a common unit before division. |
Ah, you're
|
For what it's worth, I don't think we need to restrict to the same unit. If we have |
I think you're right: I goofed here. If the numerator has a larger unit than the denominator --- and thus, a smaller value --- then That's a really nicely chosen example. |
Yes, this is why the modulo operator was changed in 0.7.0 to complement such a "broken" division according to #231 (comment). However, as we agreed, the current solution is surprising to the users, to say the least. I could provide the rule like "if both operands are integral and of the same dimension then a division of them should use a common unit" but I am not sure if that is a good idea? |
I'd be very skeptical of a special-cased rule like that. For multiplication and division generally, users can reason independently about the unit, and the result. So, for I currently think the crux of the issue is this. It is crystal clear how we should handle division (in isolation). And it's crystal clear how we should handle modulus (in isolation). The problem is that when we combine those operations, they don't cooperate the way we expect. It's true that #231 (comment) draws a compelling line in the sand: with integral rep, the quotient-remainder theorem must hold. The best idea I currently have for making that possible is to provide a separate function for quotient/remainder decomposition. This gives you an API boundary which can be a "trigger point" for that common unit conversion. You might call it like this: const auto [q, r] = quotient_and_remainder(5h, 120min); If we did this, I would expect And by using a separate function, we wouldn't interfere with the standard use cases for division or modulus, when either of them is used independently of the other. |
I've already mentioned standard function names ( |
Should we use |
How would you define it? I created a
|
Yeah... sorry I glossed over this. But thanks for prompting me to expand! What I meant here is that modulus is a sensible "pure-quantity" operation: it doesn't depend on the units. Let's momentarily restrict to positive inputs, just for simplicity. Then we could define This "subtraction-based" viewpoint also explains why the result is expressed in the common unit of the inputs. (Of course, we wouldn't actually implement it by repeated subtraction, but I think it's a useful way to define it.) |
I like your dimensional analysis in that comment, and I think your conclusions about which cases are valid (and which ones aren't) are spot-on. As to the naming, though, I don't think const auto [q, r] = div(5h, 120min); I think that argues in favor of a name like |
My understanding of the situation on the ground is that I could, however, see a case for adding a function with a new name that could work with both floating point types and integral types. It strikes me as unobjectionable, although I wouldn't bother adding it unless people ask for it. |
What is your gut feeling about modulo operation for quantities? Which of the following should be valid, and what should be the result (value and unit):
Which of them would you provide in a physical units library?
FYI, std::chrono::duration supports all of them.
The text was updated successfully, but these errors were encountered: