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

What does the % operator on floats do? #57738

Closed
AljoschaMeyer opened this issue Jan 18, 2019 · 15 comments · Fixed by #59529
Closed

What does the % operator on floats do? #57738

AljoschaMeyer opened this issue Jan 18, 2019 · 15 comments · Fixed by #59529
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools

Comments

@AljoschaMeyer
Copy link
Contributor

I can't find any documentation on the behavior of the Rem implementation for f64 (or f32). Where can I find out how it precisely works, regarding rounding modes etc? Is it the IEEE 754-2008 "remainder" operation?

@ExpHP
Copy link
Contributor

ExpHP commented Jan 18, 2019

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
  • The absolute value of a % b is in [0, b]. (yes, it CAN equal b) (actually, maybe it can't equal b. I may be mistaking it for the flooring equivalent here)

I really do feel that this stuff should be documented somewhere.

@ExpHP
Copy link
Contributor

ExpHP commented Jan 18, 2019

sorry, floor is wrong, I edited my post. The division is truncated towards zero like it is for the % operation on primitive integers.

@estebank estebank added the A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools label Jan 18, 2019
@AljoschaMeyer
Copy link
Contributor Author

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?

@frewsxcv
Copy link
Member

Not sure if this is currently documented anywhere, but at the very least, it should be mentioned here

@ExpHP
Copy link
Contributor

ExpHP commented Jan 19, 2019

@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.

@cuviper
Copy link
Member

cuviper commented Jan 24, 2019

Floating point % becomes frem in LLVM IR.

mir::BinOp::Rem => if is_float {
bx.frem(lhs, rhs)

fn frem(&mut self, lhs: &'ll Value, rhs: &'ll Value) -> &'ll Value {
self.count_insn("frem");
unsafe {
llvm::LLVMBuildFRem(self.llbuilder, lhs, rhs, noname())
}
}

@aaronfranke
Copy link

aaronfranke commented Jan 26, 2019

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.

@ExpHP
Copy link
Contributor

ExpHP commented Jan 26, 2019

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)

@DevQps
Copy link
Contributor

DevQps commented Mar 26, 2019

@AljoschaMeyer Would you still like to fix this? Otherwise I could take an attempt at improving the documentation (Y).

@AljoschaMeyer
Copy link
Contributor Author

@DevQps Please go ahead =)

@DevQps
Copy link
Contributor

DevQps commented Mar 27, 2019

@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?

@cuviper
Copy link
Member

cuviper commented Mar 27, 2019

@DevQps -- the actual operation is in the compiler, but the trait is still here:

macro_rules! rem_impl_float {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
impl Rem for $t {
type Output = $t;
#[inline]
fn rem(self, other: $t) -> $t { self % other }
}
forward_ref_binop! { impl Rem, rem for $t, $t }
)*)
}
rem_impl_float! { f32 f64 }

@steveklabnik
Copy link
Member

Beat me to it, @cuviper. Let me know if you need any help with that @DevQps; with the macro it is a bit weird

@DevQps
Copy link
Contributor

DevQps commented Mar 27, 2019

@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:

  1. 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.

  2. 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)

@steveklabnik
Copy link
Member

I'd go with option 1.

Centril added a commit to Centril/rust that referenced this issue Apr 2, 2019
Added documentation on the remainder (Rem) operator for floating points.

# Description

As has been explained in rust-lang#57738 the remainder operator on floating points is not clear.
This PR requests adds some information on how the `Rem` / remainder operator on floating points works.

Note also that this description is for both `Rem<f32> for f32` and `Rem<f64> for f64` implementations.

Ps. I wasn't really sure on how to formulate things. So please suggest changes if you have better idea's!

closes rust-lang#57738
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants