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 Mul<u64> and Div<u64> for Duration #32515

Closed
wants to merge 1 commit into from

Conversation

sfackler
Copy link
Member

This probably can't merge as-is because it breaks any code that
multiplies or divides a Duration by an unsuffixed literal :(

However, these operations are useful in many contexts (e.g. data
transfer estimation) where a u32 isn't big enough and the logic is
pretty complex, especially for multiplication. Hopefully we can expose
these in some way?

cc @rust-lang/libs

Any ideas on where to go with this functionality, @aturon?

@rust-highfive
Copy link
Collaborator

r? @alexcrichton

(rust_highfive has picked a reviewer for you, use r? to override)

@alexcrichton
Copy link
Member

Hm the breakage with unsuffixed literals indeed seems like a non-starter here.

I wonder if it'd be worth investigating the underlying type inference bug?

@sfackler
Copy link
Member Author

Not sure it's a bug strictly - there's no precedence on which integer type a literal should be if multiple apply and none are i32 - maybe we could do something like picking the smallest type that would fit the value?

@durka
Copy link
Contributor

durka commented Mar 28, 2016

Can you fix the inference regression by implementing the ops for i32, simply casting the argument to u32 and handing off to the u32 impl?

It seems to work (adds a warning though).

@sfackler
Copy link
Member Author

I'd be a bit uncomfortable with that - some_duration * -3 would compile when it really shouldn't.

@aturon
Copy link
Member

aturon commented Mar 28, 2016

@sfackler @alexcrichton

@sfackler is correct that this isn't a bug. We made the decision to allow "input" types to a trait (like type parameters) influence type inference, which is often helpful, but can be brittle when you add new impls.

It's possible that default type parameter fallback could help here, but I don't immediately see a route.

This is a kind of API evolution we certainly should allow, but the interconnecting pieces are really tricky (and the future of even default param fallback is in question). So (1) I will take this example into consideration on the lang design front and (2) meanwhile, we should figure out a different route to gain this functionality.

@alexcrichton
Copy link
Member

Bah oh well. An idea, though, perhaps?

impl<T: Into<u64>> Mul<T> for Duration {
   // ...
}

Now that we have a number of Into impls, would that work? The default fallback of i32 doesn't implement Into<u64>, however, so maybe we'd still need suffixes?

@durka
Copy link
Contributor

durka commented Mar 28, 2016

@alexcrichton I tried plugging that into my test above, it doesn't really solve the problem.

@aturon aturon added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Mar 28, 2016
@alexcrichton
Copy link
Member

The libs team discussed this PR during triage and the decision was to merge with an inherent method of a name other than mul for now. There are a number of language-level possibilities for solving this eventually, but in the near term it seems prudent to allow this form of functionality at all! We can always deprecate it at a later date if more language features become available.

Due to inference issues, these unfortunately can't be Mul and Div impls
:(
@sfackler
Copy link
Member Author

I updated the PR to move these to mul_u64 and div_u64 methods, but div_u64 is unfortunately broken. Specifically, this calculation overflows when rhs is large. Does anyone see a nice way of breaking the calculation up so that all of the numbers are small enough?

#[unstable(feature = "duration_u64", reason = "newly added", issue = "0")]
pub fn div_u64(&self, rhs: u64) -> Duration {
let secs = self.secs / rhs;
let carry = self.secs - secs * rhs;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this not simply let carry = self.secs % rhs;?

@ranma42
Copy link
Contributor

ranma42 commented Mar 31, 2016

AFAIK it requires either a muldiv-like operation (which would be the best option at least on x86), or u128 arithmetics (or using BigNum? it definitely looks like overkill) :(
Some references about software integer division can be found here and a good implementation is discussed here, but I would rather avoid putting the generic division algorithm here.

Looks like one use case for rust-lang/rfcs#1504

@alexcrichton
Copy link
Member

@sfackler maybe this function can help?

@sfackler
Copy link
Member Author

Ooh, nice, that looks like exactly what's needed! I'll update tonight.

@sfackler
Copy link
Member Author

sfackler commented Apr 1, 2016

mul_div_u64 unfortunately doesn't work since NANOS_PER_SEC * rhs is larger than a u64.

@alexcrichton
Copy link
Member

Hm can you clarify? The operation we want is carry * NANOS_PER_SEC / rhs which when calling that function will yield:

let q = carry / rhs;
let r = carry % rhs;

q * NANOS_PER_SEC + r * NANOS_PER_SEC / rhs

Which when substituted looks like:

(carry / rhs) * NANOS_PER_SEC + (carry % rhs) * NANOS_PER_SEC / rhs

Given that, where does NANOS_PER_SEC * rhs happen? In theory this wouldn't overflow unless it really does indeed overflow?

@sfackler
Copy link
Member Author

sfackler commented Apr 1, 2016

The docs on mul_div_u64 state that it "Computes (value * numer) / denom without overflow, as long as both (numer*denom) and the overall result fit into i64 (which is the case for our time conversions)", since carry % rhs is on the order of the size of rhs

@alexcrichton
Copy link
Member

Ok, well I'm basically lost in the weeds of what's going on, is this blocked on landing until that's fixed?

@sfackler
Copy link
Member Author

sfackler commented Apr 1, 2016

I think the best approach would probably be to add some intrinsics to have LLVM generate u64 * u64 -> u128 and u128 / u64 -> u64 logic for us. Doing it correctly by hand is really complex in the general case: https://github.com/sdroege/rust-muldiv/blob/master/src/u64_muldiv.rs but really simple on common architectures: https://github.com/sdroege/rust-muldiv/blob/master/src/u64_muldiv_x86_64.rs

@sfackler
Copy link
Member Author

sfackler commented Apr 3, 2016

I created a time2 crate that adds these methods and a few others - https://github.com/sfackler/rust-time2. I think it makes more sense for it to bake out there for now and then move a larger set of functionality in later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants