I've implemented the 3 variants of the modulo operation described on this Wikipedia page.
The goal is to have fully defined behavior for all inputs.
Code
Implementation with truncated division (result has the same sign as the dividend)
int32_t rem(int32_t x, int32_t y) {
if (y == 0) exit(-1);
if (y == -1) return 0;
return x % y;
}
This one is the simplest since it's already how the %
operator is implemented in C.
We have to take care of two edge cases though:
- If
y == 0
it's an invalid operation;exit(-1)
. INT32_MIN % -1
is undefined behavior, so I've hard-coded the result fory == -1
.
Implementation with floored division (result has the same sign as the divisor)
int32_t mod(int32_t x, int32_t y) {
if (y == 0) exit(-1);
if (y == -1) return 0;
return x - y * (x / y - (x % y && (x ^ y) < 0));
}
This one is a bit trickier.
We still have to handle the two previous edge cases.
However, the modulo is computed differently: x - y * floor(x / y)
.
We can't use floor
here because we're working with integers, so the formula looks like this:
x - y * (x / y - {1 if x isn't a multiple of y and the result of x / y is negative, 0 else})
x - y * (x / y - {1 if x % y and the result of x / y is negative, 0 else})
x - y * (x / y - {1 if x % y and (x or y, but not both, is negative), 0 else})
x - y * (x / y - {1 if x % y and (x ^ y) < 0, 0 else})
x - y * (x / y - (x % y && (x ^ y) < 0))
Implementation with Euclidean division (result is always positive)
Portable implementation
int32_t euc1(int32_t x, int32_t y) {
if (y == 0) exit(-1);
if (y == -1) return 0;
if (y == INT32_MIN) return x >= 0 ? x : INT32_MAX + x + 1;
y = abs(y);
return x - y * (x / y - (x < 0 && x % y));
}
It's basically the same implementation as the previous one, except that we use the absolute value of y
.
Also, (x ^ y) < 0
was simplified to x < 0
because y
is always positive (we use the absolute value).
However abs(INT32_MIN)
is undefined behavior (because abs(INT32_MIN) = INT32_MAX + 1
which is not representable) so we have to take care of this special case:
- If
x >= 0
, we havex % (INT32_MAX + 1) = x
becausex
is always inferior toINT32_MAX + 1
. - If
x < 0
, we havex % (INT32_MAX + 1) = INT32_MAX + 1 + x
, then we reorder operations to avoid overflow on signed integers which is undefined behavior:INT32_MAX + x + 1
(remember thatx
is negative).
GCC implementation
int32_t euc2(int32_t x, int32_t y) {
if (y == 0) exit(-1);
if (y == -1) return 0;
if (y != INT32_MIN) y = abs(y);
int32_t tmp = x / y - (x < 0 && x % y);
__builtin_mul_overflow(y, tmp, &tmp);
__builtin_sub_overflow(x, tmp, &tmp);
return tmp;
}
This implementation is the same as the portable one, except for the handling of the y = INT32_MIN
edge case: there is less conditional logic, making it potentially easier to optimize.
If y == INT32_MIN
we don't compute the absolute value because it's undefined behavior, then we continue the execution as nothing happened. A multiplication and a subtraction were replaced by their built-in functions counterparts to avoid undefined behavior on overflow.
Because of black magic it works:
- If
x >= 0
we havex / INT32_MIN - (x < 0 && x % INT32_MIN) = 0
, thenINT32_MIN * 0 = 0
, thenx - 0 = x
, so the result isx
as expected. - If
INT32_MIN < x < 0
we havex / INT32_MIN - (x < 0 && x % INT32_MIN) = -1
, thenINT32_MIN * -1 = INT32_MIN
(built-in function defined behavior), thenx - INT32_MIN = INT32_MAX + x + 1
(built-in function defined behavior), so the result isINT32_MAX + x + 1
as expected. - If
x == INT32_MIN
we haveINT32_MIN / INT32_MIN - (INT32_MIN < 0 && INT32_MIN % INT32_MIN) = 1
, thenINT32_MIN * 1 = INT32_MIN
, thenINT32_MIN - INT32_MIN = 0
, so the result is0
as expected.
Questions
Is there an error somewhere, something that i missed? Like a set of inputs that don't give the right result.
If this code really undefined behavior free?
Can it be simplified?