Open jhpratt opened 3 years ago
Just updated to the latest nightly, looks like this feature breaks the num-bigint crate.
That is considered acceptable breakage. Per RFC 1105, adding an inherent impl is a minor change.
Though technically not a breaking change, I wonder if it's worth delaying it landing in stable for a bit? "Everything that uses num-bigint" feels kinda large. I guess we'll see the scope of the breakage on a release-based crater run.
num-bigint
has already cut a release to fix it on their end. I suggested reverting the initial implementation on Zulip when that was pointed out. Definitely worth waiting a bit for stabilization to let things settle.
Before stabilizing these functions let's see if enough people use them often enough (otherwise it's wiser to remove them).
There's a well-established use for them via the num
crate. A number of algorithms would use div_floor
and div_ceil
, and next_multiple_of
is useful for things like block sizes. The use case absolutely exists.
The doc for checked_next_multiple_of
is incomplete:
Whoops, somehow missed that. I'll send a follow-up PR to fix the docs in a bit.
Was there an RFC for this feature or is it considered a ‘small change’?
Having proposed ceiling division myself at one point, I am now of the opinion that perhaps round-away-from-zero division would be a better primitive to have. Consider: if I need to paint a rectangle from (0, 0) to (X, Y) pixels with tiles of size x × y pixels, what are the coordinates of the farthest tile I need to draw? RAZ division generalises well to negative X and Y, ceiling division gives off-by-one answers. (In practice, I’d expect coordinates would usually be normalised to be always positive, but you never know.) I am having a hard time coming up with an opposite example where ceiling division would be advantageous over RAZ division.
No, there was no RFC for this. I brought it up on Zulip where the API was discussed to an extent.
I personally do not have a use case for ceiling division, but I also don't for next_multiple_of
. Josh mentioned that the latter could be useful for memory pages, blocks, etc., but I think we both just assumed that there's some algorithm out there that could use ceiling division. Having floor but not ceiling would be somewhat odd, which I think you would agree with. Surely we're not wrong in that there's some algorithm out there that uses ceiling division?
See https://github.com/rust-lang/rfcs/issues/2844 for people asking for ceiling division and its use cases.
Unfortunately none of the comments there (at first glance) indicate whether rounding away from zero or ceiling division is preferable, as the behavior is identical for positive integers.
Ah sorry, I missed the distinction that you were making there. I also have not seen a use case that mentions negatives so I would start with ceiling. Away from zero can probably be deferred although maybe it is simpler to just add it as well for completeness.
Feeling the pain of much breakage in the ecosystem due to num-bigint. So many things need upgrading to the newer version. From an ecosystem point of view having this as a warning before breaking nightly would have been kinder to everyone building on nightly. https://crates.io/crates/num-bigint You can see how much of the ecosystem has switched over to the new version at the bottom there. Some but not all by a long way. I know we need to make progress but a lot of people use nightly because of one or another reasons they can't quite be on stable.
From an ecosystem point of view having this as a warning before breaking nightly would have been kinder to everyone building on nightly.
Unless I'm missing something, this should currently only cause a warning: warning: once this associated item is added to the standard library, the ambiguity may cause an error or change in behavior!
So currently this only breaks builds for people who use deny(warning)
as they actually want their tests to fail to fix warnings early.
I can't see any deny(warning) in the codebase for lighthouse. I agree a warning would be perfect - it's purpose built for the job.
For those needing to fix their projects this seems the easiest way:
cargo install cargo-edit
cargo upgrade --workspace num-bigint
I can't see any deny(warning) in the codebase for lighthouse. I agree a warning would be perfect - it's purpose built for the job.
Ah, I missed that sometimes method resolution of references/not-references can mix things up: https://github.com/rust-lang/rust/issues/88878#issuecomment-917685132.
This does not work for me:
cargo install cargo-edit cargo upgrade --workspace num-bigint
Any other suggestions? This is rookie-level mistake-making
Put differently, What is the last build of num-bigint that does not break the most recent nightly?
0.3.3 and 0.4.2 are the ones that are fixed. You likely just need to run cargo update
.
Works!! You are the greatest, thanks CryZe
I've said this before, but I'm happy to send a PR temporarily reverting the int_roundings
feature if necessary. May be useful to do for generating stable & beta releases, which would limit the regressions to nightly.
On the possibility of renaming, did you consider floor_div
and ceil_div
? Or maybe floored_div
and ceiling_div
? Having the modifier as a prefix even fits better with checked_div
and others, but on the other hand we did stabilize the suffixed div_euclid
.
There's still going to be a clash with Integer::next_multiple_of
, but I'm not sure if that one is hitting in practice.
checked_div
and similar change the return type of the method, while div_euclid
etc. change the manner in which it is calculated. In this sense, it would be preferable to keep the names as-is.
I don't think there's a nice way around Integer::next_multiple_of
, but it would probably be worth a crater run prior to stabilization.
That distinction is still blurry, since wrapping_div
also only changes the manner (for the sole case of signed MIN/-1
).
Granted. I didn't check the full API before commenting; just off the top of my head. Looking at the docs now, there's checked
, overflowing
, saturating
, and wrapping
, all of which are prefix. euclid
is the sole method using a suffix.
To be honest I really don't care whether it's called div_floor
or floor_div
. Either is clear and unambiguous. I likely went for div_floor
because that's what's used in the num
crate and is what I've used as the name for an internal macro in my code.
I don't have any strong preference on next_multiple_of
, and if there's an equally obvious name that's fine. next_multiple_of
seems like the right name, though. If we can continue using that name without too much disruption, that seems preferable.
If you are confused about why div_floor
and div_ceil
methods are prefixed with unstable_
in the current nightly, see #88971.
Few questions:
The current div_ceil
implementation uses the modulo operator which is expensive. It can be replaced with (self + d - 1) / d
which is a much faster version of ceiling an unsigned integer.
@Andy-Python-Programmer That would require some care to avoid overflow, though.
In practice, targets often provide either a combined integer-division-and-remainder instruction (like x86), a combined division-and-remainder support library function (32-bit ARM EABI) or, if division and remainder are separate instructions, the CPU can perform macro-instruction fusion (like RISC-V and AArch64). Optimisers are generally capable of reusing both results of this combined operation when both division and modulo are present in user code. So with a half-decent optimiser, performing both division and modulo should not be any more expensive than division alone.
(Although another alternative would be if self { (self - 1) / d + 1 } else { 0 }
, much less likely to overflow.)
Can .div_ceil()
panic only if debug_assertions
is enabled, so that it's analogous to regular .div()
?
Otherwise it's not a zero-cost abstraction compared to (x + d - 1) / d
, and I wouldn't use it where code size or autovectorization is important.
@kornelski As far as I can tell, div_ceil
should panic in exactly the cases div
does. Is there a panicking case I'm missing?
Otherwise it's not a zero-cost abstraction compared to
(x + d - 1) / d
, and I wouldn't use it where code size or autovectorization is important.
That has more chance of failure (panic or overflow) when computing the numerator, whereas div_ceil
tries to be correct in all cases. If that cost is not acceptable to you, then you really should use your expression instead.
Stabilization PR up: #94455.
num-integer
. This is an "allowed" form of compatibility issue (because otherwise we could never add new functions), but we decided it was too much ecosystem breakage: https://github.com/rust-lang/rust/issues/88971num-integer
version explicitly, to avoid the compatibility issue.A summary of the API is included at the top of this tracking issue.
Example Usages
Many people have used these functions as part of num-integer
; we're expecting that (subject to MSRV requirements) users can use the same functions as part of the standard library. A few folks reported converting their code over, while the functions were on nightly, and observed no issues doing so.
I think, at this point, we can reasonably stabilize these functions. We have evidence that people want these functions, and we have the infrastructure in place to avoid breakage. This also seems like a good time, since we just shipped 1.59 and 1.60 beta just branched, so we can put these into 1.61 and give the stabilization plenty of time to bake there.
Shall we stabilize these four functions on integers?
@rfcbot merge
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:
Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
Thank you for the fantastic summary of the history @joshtriplett!!
@rfcbot concern Should these panic or wrap around on overflow in release mode?
The docs of these say This function will panic if [..] the operation results in overflow.
, but the implementation seems to use #[rustc_inherit_overflow_checks]
and will only panic in debug mode, and silently overflow/wrap in release mode.
Division overflow panics in release mode too.
I would expect these to panic iff overflow checks are enabled (except in the case of divide-by-zero, for which I'd expect unconditional panic).
@joshtriplett Do you mean that you expect i32::MIN.div_floor(-1)
and i32::MIN.div_ceil(-1)
to return a wrapped value in release mode?
With the code merged in #88581, these are the panics I think are possible:
x.div_floor(0)
, x.div_ceil(0)
, x.next_multiple_of(0)
; these panics are not controversial.i::MIN.div_floor(-1)
, i::MIN.div_ceil(-1)
; I believe these should be consistent with i::MIN.div(-1)
.x.next_multiple_of(y)
does not panic for i32::MIN.next_multiple_of(-1)
as this case is already explicitly handled. It can panic when the result overflows and the panic can occur in the final addition only, and since it's an addition, the "panic on debug, wrap on release" behavior should be maintained. (Edit: I see this is the main concern raised; as the documentation for next_multiple_of
indicates that this would panic, while the implementation only panics with overflow checks. While I think the agreement is this is correct, the documentation does not reflect it.)My analysis of next_multiple_of
with my notes marked Note:
follows:
// This would otherwise fail when calculating `r` when self == T::MIN.
if rhs == -1 {
return self;
}
let r = self % rhs;
// Note: If rhs = 0, a panic has already happened.
// Note: At this point, abs(r) < abs(rhs).
let m = if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) {
// Note: signum(r) != signum(rhs), and abs(r) < abs(rhs), so this branch
// will leave abs(m) < abs(rhs) and signum(m) = signum(rhs).
// Note: can never overflow since r and rhs have opposite signs.
r + rhs
} else {
// Note: in this branch, abs(m) < abs(rhs). Also, either m is 0, or
// signum(m) = signum(rhs)
r
};
if m == 0 {
// Note: no overflow here.
self
} else {
// Note: (rhs - m) can never overflow since rhs and m have similar signs.
// Note: overflow possible in the final addition.
self + (rhs - m)
}
By the way, in checked_next_multiple_of
there are two unnecessary try_opt!
calls.
try_opt!(r.checked_add(rhs))
should never fail since r
and rhs
have opposite signs.try_opt!(rhs.checked_sub(m))
should never fail since rhs
and m
have the same sign.@tspiteri I don't have any expectations regarding the signed versions; I have no plans to use them. I think next_multiple_of(-n)
seems excessively confusing and I have no idea what semantics I'd expect there.
next_multiple_of(-n)
has the following description. It's basically prev_multiple_of
.
If
rhs
is negative, calculates the largest value less than or equal toself
that is a multiple ofrhs
.
This matches my intuition (which should be obvious given that I'm the one that implemented it).
next_multiple_of(-n)
has the following description. It's basicallyprev_multiple_of
.If
rhs
is negative, calculates the largest value less than or equal toself
that is a multiple ofrhs
.This matches my intuition (which should be obvious given that I'm the one that implemented it).
Shouldn't that be "the largest value less than or equal to self
that is a multiple of rhs.abs()
"?
In any case, that still seems like confusing behavior, to the point that any code using it would necessitate a trip to the documentation to understand.
Well, it's still a multiple, just a negative multiple.
I presume everyone agrees that division by zero should panic. Simple division panics in release mode as well (i32::MIN / -1
is a test case). I think it makes sense to maintain this behavior for div_floor
and div_ceil
.
checked_next_multiple_of
should never panic, given that its purpose is to do checked arithmetic. As such, the only question is whether next_multiple_of
should panic in release mode.
Initially, I'd have said that wrapping was reasonable. This is because it would be most performant; code that can panic is not inlined often. While wrapping is sometimes the desired behavior in integer arithmetic, I don't see a similar use case here. Unless someone is able to articulate one, I think the best way forward is to change the current behavior and bring it in line with documentation: panic on overflow in release mode. If the user has desired behavior that is different, they would be free to use .checked_next_multiple_of(x).unwrap_or(y)
.
How do others feel about this?
If next_multiple_of
is defined as a + (b - a % b)
, and a % 0
is defined to equal a
(as surprisingly makes sense mathematically), it follows that a.next_multiple_of(0)
should be zero. But I don't think much code using the method will expect that. On the contrary, I think more users will expect the definition of a.next_multiple_of(b)
= inf { x ∈ ℤ : b
∣ x ∧ x sgn(b
) > a
sgn(b
) } (or just x > a
, which is easily implementable in terms of that), in which case, there is no possible value to assign. It better panic for zero after all.
But the same definition can straightforwardly justify a.next_multiple_of(-1) == a - 1
, and I see no reason not to extend that to all values other than short-term concessions to performance. And I would guess much of the time, b
is going to be a compile-time constant anyway, in which case any extra branch to handle −1 can be eliminated.
Feature gate:
#![feature(int_roundings)]
This is a tracking issue for the
div_floor
,div_ceil
,next_multiple_of
, andchecked_multiple_of
methods on all integer types.Public API
Steps / History
Unresolved Questions