开发者

Python 3 integer division. How to make math operators consistent with C

I need to port quite a few formulas from C to Python and vice versa. What is the best way to make sure that nothing breaks in the process?

I am primarily worried about automatic int/int 开发者_如何学编程= float conversions.


You could use the // operator. It performs an integer division, but it's not quite what you'd expect from C:

A quote from here:

The // operator performs a quirky kind of integer division. When the result is positive, you can think of it as truncating (not rounding) to 0 decimal places, but be careful with that.

When integer-dividing negative numbers, the // operator rounds “up” to the nearest integer. Mathematically speaking, it’s rounding “down” since −6 is less than −5, but it could trip you up if you were expecting it to truncate to −5.

For example, -11 // 2 in Python returns -6, where -11 / 2 in C returns -5. I'd suggest writing and thoroughly unit-testing a custom integer division function that "emulates" C behaviour.

The page I linked above also has a link to PEP 238 which has some interesting background information about division and the changes from Python 2 to 3. There are some suggestions about what to use for integer division, like divmod(x, y)[0] and int(x/y) for positive numbers, perhaps you'll find more useful things there.


In C:

-11/2 = -5

In Python:

-11/2 = -5.5

And also in Python:

-11//2 = -6

To achieve C-like behaviour, write int(-11/2) in Python. This will evaluate to -5.


Some ways to compute integer division with C semantics are as follows:

def div_c0(a, b):
    if (a >= 0) != (b >= 0) and a % b:
        return a // b + 1
    else:
        return a // b
def div_c1(a, b):
    q, r = a // b, a % b
    if (a >= 0) != (b >= 0) and r:
        return q + 1
    else:
        return q
def div_c2(a, b):
    q, r = divmod(a, b)
    if (a >= 0) != (b >= 0) and r:
        return q + 1
    else:
        return q
def mod_c(a, b):
    return (a % b if b >= 0 else a % -b) if a >= 0 else (-(-a % b) if b >= 0 else a % b)


def div_c3(a, b):
    r = mod_c(a, b)
    return (a - r) // b

With timings:

import itertools


n = 100
l = [x for x in range(-n, n + 1)]
ll = [(a, b) for a, b in itertools.product(l, repeat=2) if b]


funcs = div_c0, div_c1, div_c2, div_c3
for func in funcs:
    correct = all(func(a, b) == funcs[0](a, b) for a, b in ll)
    print(f"{func.__name__}  correct:{correct}  ", end="")
    %timeit [func(a, b) for a, b in ll]
# div_c0  correct:True  100 loops, best of 5: 10.3 ms per loop
# div_c1  correct:True  100 loops, best of 5: 11.5 ms per loop
# div_c2  correct:True  100 loops, best of 5: 13.2 ms per loop
# div_c3  correct:True  100 loops, best of 5: 15.4 ms per loop

Indicating the first approach to be the fastest.


For implementing C's % using Python, see here.


In the opposite direction:

Since Python 3 divmod (or //) integer division requires the remainder to have the same sign as divisor at non-zero remainder case, it's inconsistent with many other languages (quote from 1.4. Integer Arithmetic).

To have your "C-like" result same as Python, you should compare the remainder result with divisor (suggestion: by xor on sign bits equals to 1, or multiplication with negative result), and in case it's different, add the divisor to the remainder, and subtract 1 from the quotient.

        // Python Divmod requires a remainder with the same sign as the divisor for
        // a non-zero remainder

        // Assuming isPyCompatible is a flag to distinguish C/Python mode
        isPyCompatible *= (int)remainder;
        if (isPyCompatible)
        {
            int32_t xorRes = remainder ^ divisor;
            int32_t andRes = xorRes & ((int32_t)((uint32_t)1<<31));
            if (andRes)
            {
                remainder += divisor;
                quotient -= 1;
            }
        }

(Credit to Gawarkiewicz M. for pointing this out.)


You will need to know what the formula does, and understand both the C implementation and how to implement it in Python. But unless you are doing integer maths it should be quite similar, and if you are doing integer maths, the question is why. :)

Integer maths are either done because of some specific purpose, often related to computers, or because it's faster than floats when doing massive computations, like Fractint does for fractals, and in that case Python is usually not the right choice. ;)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜