Вычислить C '% `, используя Python'` ``? - PullRequest
3 голосов
/ 21 апреля 2020

Как мне вычислить C %, используя Python '%? Разница между ними заключается в том, как они обрабатывают случай отрицательных аргументов.

В обоих языках % определяется таким образом, что это соотношение (// является целочисленным делением):

a // b * b + a % b == a

, но округление a // b отличается в C и Python, что приводит к другому определению a % b.

Например, в C (где целочисленное деление просто / с int операндами) у нас есть:

int a = 31;
int b = -3;
a / b;  // -10
a % b;  // 1

в то время как в Python:

a = 31
b = -3
a // b  # -11
a % b  # -2

Я знаю об этом вопрос , который касается противоположного (то есть, как вычислить Python * % из C * %) и содержит дополнительные обсуждения.

Мне также известно о * Модуль 1043 * 3.7 math представляет remainder(), но его результат равен float, а не int, и, следовательно, он не будет иметь произвольной точности.

Ответы [ 3 ]

4 голосов
/ 21 апреля 2020

Некоторые способы могут быть:

def mod_c0(a, b):
    if b < 0:
        b = -b
    return -1 * (-a % b) if a < 0 else a % b
def mod_c1(a, b):
    return (-1 if a < 0 else 1) * ((a if a > 0 else -a) % (b if b > 0 else -b))
def mod_c2(a, b):
    return (-1 if a < 0 else 1) * (abs(a) % abs(b))
def mod_c3(a, b):
    r = a % b
    return (r - b) if (a < 0) != (b < 0) and r != 0 else r
def mod_c4(a, b):
    r = a % b
    return (r - b) if (a * b < 0) and r != 0 else r
def mod_c5(a, b):
    return a % (-b if a ^ b < 0 else b)
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))
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))
def mod_c8(a, b):
    q, r = divmod(a, b)
    if (a >= 0) != (b >= 0) and r:
        q += 1
    return a - q * b
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

, которые все работают как положено, Например:

print(mod_c0(31, -3))
# 1

По существу, mod_c0() реализует оптимизированную версию mod_c1() и mod_c2(), которые идентичны, за исключением того, что в mod_c1() вызов (относительно дорогой) вызов abs() заменяется троичным условным оператором с тем же семантием c. Вместо этого mod_c3() и mod_c4() пытаются напрямую исправить значение a % b для случаев, когда это необходимо. Разница между ними заключается в том, как они обнаруживают противоположные знаки аргументов: (a < 0) != (b != 0) против a * b < 0. Подход mod_c5() основан на ответе @ ArborealAnole и по существу использует побитовый xor для правильной обработки случаев, тогда как mod_c6() и mod_c7() совпадают с @ ответом ArborealAnole , но с использованием адаптивного правого сдвига с int.bit_length(). Подход mod_c8() использует исправленное определение целочисленного деления, чтобы зафиксировать значение модуля. Метод mod_c9() основан на ответе @ NeverGoodEnough и по существу становится полностью условным.


Охватывает все знаковые случаи:

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))
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             

Немного больше тестов и тестов:

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()
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

Возможно, есть лучшие (более короткие ?, более быстрые?) Способы, учитывая, что реализация Python '* % использование C % выглядит намного проще:

((a % b) + b) % b

Чтобы получить представление о том, как вычисление 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

Обратите внимание, конечно, что mod_c2py() полезен только для того, чтобы получить ощущение какие характеристики мы можем ожидать от функции mod_c().


( EDITED для исправления некоторых из предложенных методов и включения некоторых моментов времени)

( EDITED-2 для добавления mod_c5() решения)

( EDITED-3 для добавления mod_c6() к mod_c9() решениям)

1 голос
/ 23 апреля 2020

Я слежу за исчерпывающим ответом @ norok2. Я попробовал супер-наивный подход с ветвями, и он, кажется, немного, но последовательно быстрее (~ 2-4%). ​​

def mod_naive(x,y):
  if y < 0:
    if x < 0:
      return x%y
    else:
      return (x%-y)
  else:
    if x < 0:
      return -(-x%y)
    else:
      return x%y

или с лямбдой (не влияет на скорость, только на прохладу) :

mod_naive = lambda x,y: (x%y if x < 0 else x%-y) if y < 0 else (-(-x%y) if x < 0 else x%y)

По сравнению с самым быстрым решением @ norok2 (mod_c0):

mod_c0 correct: True
100 loops, best of 3: 6.86 ms per loop

mod_naive correct: True
100 loops, best of 3: 6.58 ms per loop

Мое (наивное) предположение о том, почему ветвь алгоритмы прогнозирования в конечном итоге будут производить меньше операций.

1 голос
/ 22 апреля 2020

Для 64-разрядных целых чисел должно работать любое из этих значений:

def mod_c_AA0(a,b):
    x=(a^b)>>63
    return a % (b*(x|1))
def mod_c_AA1(a,b):
    x=(a^b)>>63
    return a % ((-b & x)|(b & ~x))

с использованием двоичного двоичного дополнения. Как предлагает norok2, замените a_xor_b=a^b; x=a_xor_b>>a_xor_b.bit_length(); на первую строку, чтобы иметь оптимальную специфичность при сдвиге битов в зависимости от величины a и b.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...