Closed AljoschaMeyer closed 5 years ago
Is it the IEEE 754-2008 "remainder" operation?
Definitely not.
%
on floats is weird because it's a modulus operation that has no corresponding integer division operation in the standard library. Its integral division function (which for the sake of discourse I will call a /& b
) is the following:
a /& b = f64::trunc(a / b)
and when I refer to this as its corresponding division function, I mean that /&
and %
approximately obey the following law (which is common for pairs of integer division and modulus operations):
a = (a /& b) * b + (a % b)
I believe it is the same as the C function fmod
.
Other notable properties:
a % b
has the sign of a
a % b
is in [0, b]
. b
)b
. I may be mistaking it for the flooring equivalent here)I really do feel that this stuff should be documented somewhere.
sorry, floor
is wrong, I edited my post. The division is truncated towards zero like it is for the %
operation on primitive integers.
Thank you @ExpHP. For a bit more context: Where could I have found that information myself? And how did it come about that rust diverges from IEEE 754 here - is this llvm semantics cropping up?
Not sure if this is currently documented anywhere, but at the very least, it should be mentioned here
@AljoschaMeyer to be honest, this is more or less how I naturally suspected it would work, based on experience with a similar %
operator in Python (although that language actually has a corresponding integer division operation on floats, spelled //
). Beyond that, I simply verified my suspicions by running examples on the playground.
Your post is the very first time I've ever heard that IEEE-754 specifies a modulus operation. Its definition (as the partner to round(a / b)
) is not something I've ever noticed appearing in the standard library of any language (let alone being implemented as a builtin binary operator!). And speaking personally, although I've used this operation in the past, I have a strong distaste for it as people in my field of research (crystal physics) often use it incorrectly.
I'm not sure what provides the implementation, or if it is even correctly rounded. However, one can see from the trivial trait impl that it is a compiler-builtin operation that likely lowers to some intrinsic.
Would it be possible to implement a canonical modulus function, perhaps %%
? Such that:
5 %% 3
returns 2
-5 %% 3
returns 1
5 %% -3
returns -1
-5 %% -3
returns -2
The rule is that the output wraps around the second argument on a range [0, b)
. If the first argument is already on said range it will be output with no change, such that -2 %% -3
returns -2
.
There is an effort to add euclidean modulus and division to all numerical types. These define the modulus to lie in [0, abs(b))
, and are what I deem the "canonical" modulus function, rather than yours which is the floored modulus.
(worth noting: Both yours and my definition are typically capable of producing a value that is exactly equal to abs(b)
for floating point numbers in most implementations, as the result of -1e-20 mod b
)
@AljoschaMeyer Would you still like to fix this? Otherwise I could take an attempt at improving the documentation (Y).
@DevQps Please go ahead =)
@steveklabnik I'd like to fix this, and since there were no other assignee's I didn't know who else to ping :)
I searched libstd
and libcore
for an implementation of Rem<f32>
but nothing came up. I guess the reason for that is that it's builtin in the compiler? I was wondering is there any place where I can add documentation to this trait implementation? Or are we stuck here?
@DevQps -- the actual operation is in the compiler, but the trait is still here: https://github.com/rust-lang/rust/blob/33ef0bad21d6bb646c7c3ab0dbf381ca96c324bf/src/libcore/ops/arith.rs#L538-L552
Beat me to it, @cuviper. Let me know if you need any help with that @devqps; with the macro it is a bit weird
@cuviper @steveklabnik Thanks for the info! I took a look at it (and looked some information up about macro's as well). I see these solutions:
Write generic documentation inside rem_impl_float
that covers both impl Rem<f32>
and impl Rem<f64>
. With this I mean that we use the term "float" instead of the more concrete "f32" and "f64". For as far as I know, the % operations do not differ between these types.
Unroll (remove and replace with concrete impl) rem_impl_float
such that we can write more concrete documentation for impl Rem<f32>
and impl Rem<f64>
.
Additional question: forward_ref_binop!
generates: Rem<&f32> for f32
, Rem<f32> for &f32
and Rem<&f32> for &f32
but is used by many other functions as well. Should we leave those without documentation? Or should we also unroll those? I could also adjust forward_ref_binop!
to take an additional doc string but I guess we would have to go and add doc strings all over the place (since it's used for other operations as well) so I think that is not a good idea.
I wonder what you think is the best, and then I will go and create a Pull Request! (Unless you also have open points of course)
I'd go with option 1.
I can't find any documentation on the behavior of the
Rem
implementation forf64
(orf32
). Where can I find out how it precisely works, regarding rounding modes etc? Is it the IEEE 754-2008 "remainder" operation?