-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
builtin functions for integer division and remainder division #217
Comments
Proposal:
|
Trying to think of any objections, I suppose I could point out that the name "rem" is somewhat ambiguous. In Batch, "rem" is short for "remark" and starts a single-line comment, but I really don't think we need to worry about that. I do think that out of context, "rem" is usually short for "remove" rather than "remainder", but perhaps the context would be enough to clarify the meaning of the Zig function. I like the My hunch of what we should do is make So here's a completely specified definition of both functions:
(Mathematically, Speaking of "some integer |
Here's a radical proposal: allow a For integer division, we add builtin functions for different variants:
Negative divisor cases for division (as opposed to remainder) are pretty well defined by math. In all variants of integer division We could also do a Here's my worksheet to make sure the above proposal is coherent:
Here's a userland implementation of fn divFloor(a: i32, b: i32) -> i32 {
%%divFloorOverflow(a, b)
}
fn divFloorOverflow(a: i32, b: i32) -> %i32 {
assert(b != 0);
if (b < 0) {
// canonicalize the sign of the divisor
return divFloorOverflow(
// note that some "overflow" cases might actually be capable of returning mathematically correct results,
// but it's easier for implementations to define division such that negation overflow cases are also overflow cases for the division.
%return math.mulOverflow(a, -1),
%return math.mulOverflow(b, -1),
);
}
if (a >= 0) {
// we've already checked for negative b, so overflow cannot happen here
return @divTrunc(a, b);
} else {
return -@divTrunc(
// see above comment about overflow
%return math.addOverflow(
%return math.mulOverflow(a, -1), b - 1),
b,
);
}
} Here are test cases that hit each of the
|
This proposal goes nicely with the modulus/remainder proposal. An alternate set of proposals:
|
Is it safe to claim (in a Zig user guide, for example) that With that in mind, I'm not comfortable encouraging users to use slower functions when they might know that they don't need the negative cases at all. One happy coincidence of my proposal above is that all three functions have the same size name. This means Zig is not biasing programmers to use any of them over the others. As an additional bonus, when programmers find out that there's no It might be worth noting that for unsigned integers, |
I agree with your reasoning here, and I think this is a pretty good argument for accepting your proposal, where integer division is done via builtin functions, not the |
One more thing: should we allow |
More generally, we could allow But that actually leaves out |
Fun fact, we already have |
Nice. So that leaves the question of if we should allow I'll throw in the elephant in the room argument, which is that the All that being said, I like the idea of making programmers choose between Let's take a look at some "real world" Zig code for inspiration: https://github.com/andrewrk/tetris/blob/91dfddb6c96245429737b0b402b8ea67c49a8510/src/main.zig#L337 This is code to center the display of text by dividing the pixel width of the text in half and the width of the display area in half. What happens if either of these widths is odd? In that specific case, neither can be odd, so The above (admittedly specific) usecase demonstrates that there are many possible things that you could mean when doing something as simple as centering the display of something. My mandatory verbosity proposal makes that slightly more clear to a code reader. But is it worth it? Again, I don't feel comfortable making a judgement call until I subject myself to the pain to see how it feels. |
Fun fact: |
To recap, here are the changes going forward:
|
Did you mean |
yes. i still think that case hits the overflow i intended though. there's an intermediate result of 0x80000001 in there i think. |
Here's my divFloor implementation. I believe it does not have the overflow problems: fn divFloor(comptime T: type, a: T, b: T) -> T {
const result = @divTrunc(a, b);
if (result >= 0 or result * b == a)
return result;
else
return result - 1;
} |
let's define the modulus operator to be euclidean mod. here is some pre-written justification: https://eev.ee/blog/2016/12/01/lets-stop-copying-c/#negative-modulo
We can provide an implementation that achieves this regardless of what instructions are actually available on the system.
If a programmer wants to use a modulus that has undefined behavior for a negative operand, we can provide a builtin for that, and it can have a debug safety check.
The text was updated successfully, but these errors were encountered: