使用 Python 的 `%` 计算 C 的 `%`?

nor*_*ok2 4 c python modulus

如何%使用 Python计算 C %?两者之间的区别在于它们处理否定论点的方式。

在这两种语言中, 的%定义方式使这种关系(//整数除法)成立:

a // b * b + a % b == a
Run Code Online (Sandbox Code Playgroud)

但是a // bC 和 Python 中的舍入不同,导致a % b.

例如,在 C 中(其中整数除法仅/使用int操作数)我们有:

int a = 31;
int b = -3;
a / b;  // -10
a % b;  // 1
Run Code Online (Sandbox Code Playgroud)

在 Python 中:

a = 31
b = -3
a // b  # -11
a % b  # -2
Run Code Online (Sandbox Code Playgroud)

我知道这个问题,它解决了相反的问题(即如何%从 C计算 Python %)并包含其他讨论。

我也知道math引入了 Python 3.7模块,remainder()但它的结果是 a float,而不是 an int,因此它不会享受任意精度。

nor*_*ok2 5

一些方法是:

def mod_c0(a, b):
    if b < 0:
        b = -b
    return -1 * (-a % b) if a < 0 else a % b
Run Code Online (Sandbox Code Playgroud)
def mod_c1(a, b):
    return (-1 if a < 0 else 1) * ((a if a > 0 else -a) % (b if b > 0 else -b))
Run Code Online (Sandbox Code Playgroud)
def mod_c2(a, b):
    return (-1 if a < 0 else 1) * (abs(a) % abs(b))
Run Code Online (Sandbox Code Playgroud)
def mod_c3(a, b):
    r = a % b
    return (r - b) if (a < 0) != (b < 0) and r != 0 else r
Run Code Online (Sandbox Code Playgroud)
def mod_c4(a, b):
    r = a % b
    return (r - b) if (a * b < 0) and r != 0 else r
Run Code Online (Sandbox Code Playgroud)
def mod_c5(a, b):
    return a % (-b if a ^ b < 0 else b)
Run Code Online (Sandbox Code Playgroud)
def mod_c6(a, b):
    a_xor_b = a ^ b
    n = a_xor_b.bit_length()
    x = a_xor_b >> n
    return a % (b * (x | 1))
Run Code Online (Sandbox Code Playgroud)
def mod_c7(a, b):
    a_xor_b = a ^ b
    n = a_xor_b.bit_length()
    x = a_xor_b >> n
    return a % ((-b & x) | (b & ~x))
Run Code Online (Sandbox Code Playgroud)
def mod_c8(a, b):
    q, r = divmod(a, b)
    if (a >= 0) != (b >= 0) and r:
        q += 1
    return a - q * b
Run Code Online (Sandbox Code Playgroud)
def mod_c9(a, b):
    if a >= 0:
        if b >= 0:
            return a % b
        else:
            return a % -b
    else:
        if b >= 0:
            return -(-a % b)
        else:
            return a % b
Run Code Online (Sandbox Code Playgroud)

这一切都按预期工作,例如:

print(mod_c0(31, -3))
# 1
Run Code Online (Sandbox Code Playgroud)

本质上,mod_c0()实现了mod_c1()and的优化版本,mod_c2()除了在mod_c1()调用中(相对昂贵的)调用中abs()被替换为具有相同语义的三元条件运算符之外,它们是相同的。相反,mod_c3()mod_c4()尝试a % b在需要的情况下直接修复该值。两者之间的区别在于它们如何检测参数的相反符号:(a < 0) != (b != 0)vs a * b < 0。该mod_c5()方法的灵感来自@ArborealAnole 的答案,本质上使用按位异或来正确处理案例,而mod_c6()mod_c7()@ArborealAnole 的答案相同,但使用自适应右移与int.bit_length(). 这mod_c8()方法使用修正的整数除法定义来修正模值。该mod_c9()方法的灵感来自@NeverGoodEnough's answer,并且基本上是完全有条件的。


涵盖所有标志案例:

vals = (3, -3, 31, -31)
s = '{:<{n}}' * 4
n = 14
print(s.format('a', 'b', 'mod(a, b)', 'mod_c(a, b)', n=n))
print(s.format(*(('-' * (n - 1),) * 4), n=n))
for a, b in itertools.product(vals, repeat=2):
    print(s.format(a, b, mod(a, b), mod_c0(a, b), n=n))
Run Code Online (Sandbox Code Playgroud)
a             b             mod(a, b)     mod_c(a, b)   
------------- ------------- ------------- ------------- 
3             3             0             0             
3             -3            0             0             
3             31            3             3             
3             -31           -28           3             
-3            3             0             0             
-3            -3            0             0             
-3            31            28            -3            
-3            -31           -3            -3            
31            3             1             1             
31            -3            -2            1             
31            31            0             0             
31            -31           0             0             
-31           3             2             -1            
-31           -3            -1            -1            
-31           31            0             0             
-31           -31           0             0             
Run Code Online (Sandbox Code Playgroud)

更多的测试和基准测试:

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

funcs = mod_c0, mod_c1, mod_c2, mod_c3, mod_c4, mod_c5, mod_c6, mod_c7, mod_c8, mod_c9
for func in funcs:
    correct = all(func(a, b) == funcs[0](a, b) for a, b in ll)
    print(func.__name__, 'correct:', all_equal)
    %timeit [func(a, b) for a, b in ll]
    print()
Run Code Online (Sandbox Code Playgroud)
mod_c0 correct: True
100 loops, best of 3: 6.6 ms per loop

mod_c1 correct: True
100 loops, best of 3: 7.86 ms per loop

mod_c2 correct: True
100 loops, best of 3: 8.49 ms per loop

mod_c3 correct: True
100 loops, best of 3: 7.56 ms per loop

mod_c4 correct: True
100 loops, best of 3: 7.5 ms per loop

mod_c5 correct: True
100 loops, best of 3: 7.94 ms per loop

mod_c6 correct: True
100 loops, best of 3: 13.4 ms per loop

mod_c7 correct: True
100 loops, best of 3: 16.8 ms per loop

mod_c8 correct: True
100 loops, best of 3: 12.4 ms per loop

mod_c9 correct: True
100 loops, best of 3: 6.48 ms per loop
Run Code Online (Sandbox Code Playgroud)

也许有更好(更短?更快?)的方法,因为%使用 C 的Python 实现%似乎要简单得多:

((a % b) + b) % b
Run Code Online (Sandbox Code Playgroud)

要获得C风格怎么有的感觉%计算(mod_c*()从上面的功能)表示对一般的%或所需的操作来获得Python风格%来自C

def mod_py(a, b):
    return a % b

def mod_c2py(a, b):
    return ((a % b) + b) % b


%timeit [mod_py(a, b) for a, b in ll]
# 100 loops, best of 3: 5.85 ms per loop
%timeit [mod_c2py(a, b) for a, b in ll]
# 100 loops, best of 3: 7.84 ms per loop
Run Code Online (Sandbox Code Playgroud)

当然,请注意,这mod_c2py()仅有助于了解我们可以从mod_c()函数中获得的性能。


编辑以修复一些建议的方法并包括一些时间)

EDITED-2添加mod_c5()解决方案)

EDITED-3添加mod_c6()mod_c9()解决方案)