Чому (% 256) відрізняється від (a & 0xFF)?


145

Я завжди припускав, що, роблячи (a % 256)оптимізатор, природно, буде використовувати ефективну побітну операцію, як ніби я писав (a & 0xFF).

Під час тестування на компіляторі explorer gcc-6.2 (-O3):

// Type your code here, or load an example.
int mod(int num) {
    return num % 256;
}

mod(int):
    mov     edx, edi
    sar     edx, 31
    shr     edx, 24
    lea     eax, [rdi+rdx]
    movzx   eax, al
    sub     eax, edx
    ret

І при спробі іншого коду:

// Type your code here, or load an example.
int mod(int num) {
    return num & 0xFF;
}

mod(int):
    movzx   eax, dil
    ret

Здається, я щось зовсім пропускаю. Будь-які ідеї?


64
0xFF - 255, а не 256.
Rishikesh Raje

186
@RishikeshRaje: Так? теж %не є &.
usr2564301

27
@RishikeshRaje: Я впевнений, що ОП це дуже добре усвідомлює. Вони використовуються при різних операціях.
Ура та хт. - Альф

28
З інтересу ви отримуєте кращі результати, якщо numє unsigned?
Вірсавія

20
@RishikeshRaje Побітові та 0xFF еквівалентні модулю 2 ^ 8 для непідписаних цілих чисел.
2501

Відповіді:


230

Це не те саме. Спробуйте num = -79, і ви отримаєте різні результати від обох операцій. (-79) % 256 = -79, в той час (-79) & 0xffяк деяке додатне число.

Використовуючи unsigned int, операції однакові, і код, ймовірно, буде однаковим.

PS- Хтось прокоментував

Вони не повинні бути однаковими, a % bвизначається як a - b * floor (a / b).

Це не так, як це визначено в C, C ++, Objective-C (тобто у всіх мовах, з яких збирається код у запитанні).


Коментарі не для розширеного обговорення; ця розмова була переміщена до чату .
Martijn Pieters

52

Коротка відповідь

-1 % 256врожайність, -1а не те, 255що є -1 & 0xFF. Тому оптимізація була б неправильною.

Довга відповідь

C ++ має умову про те (a/b)*b + a%b == a, що здається цілком природним. a/bзавжди повертає арифметичний результат без дробової частини (зрізається в бік 0). Як наслідок, a%bмає такий самий знак, що aабо 0.

Підрозділ -1/256дає 0і , отже , -1%256має бути -1, щоб задовольнити цю умову ( (-1%256)*256 + -1%256 == -1). Це явно відрізняється від того, -1&0xFFщо є 0xFF. Тому компілятор не може оптимізувати потрібний вам спосіб.

У відповідному розділі стандарту C ++ [expr.mul §4] станом на N4606 зазначено:

Для інтегральних операндів /оператор отримує алгебраїчний коефіцієнт з відхиленою дробовою частиною; якщо показник a/bє репрезентативним за типом результату, (a/b)*b + a%bдорівнює a[...].

Включення оптимізації

Однак, використовуючи unsignedтипи, оптимізація була б цілком правильною , задовольняючи вищезгаданій умові:

unsigned(-1)%256 == 0xFF

Дивіться також це .

Інші мови

Це обробляється дуже різними для різних мов програмування, оскільки ви можете шукати у Вікіпедії .


50

Оскільки C ++ 11, num % 256має бути непозитивним, якщо numнегативним.

Таким чином, бітова модель залежатиме від реалізації підписаних типів у вашій системі: для першого негативного аргументу результатом є не вилучення найменш значущих 8 біт.

Було б іншим питанням, якби numу вашому випадку було unsigned: в ці дні я майже очікував, що компілятор здійснить оптимізацію, яку ви цитуєте.


6
Майже, але не зовсім. Якщо numвід’ємник, то num % 256це нуль або від’ємник (він же негативний).
Наюкі

5
Який ІМО, є помилкою в стандарті: математично модульна операція повинна приймати знак дільника, 256 в цьому випадку. Для того, щоб зрозуміти, чому вважати це (-250+256)%256==6, але (-250%256)+(256%256)повинно бути, згідно стандарту, "непозитивним", а отже, ні 6. Порушення подібної асоціативності має побічні ефекти в реальному житті: наприклад, при обчисленні "зменшення масштабу" візуалізації в цілих координатах потрібно змістити зображення, щоб усі координати були негативними.
Майкл

2
@Michael Modulus ніколи не розповсюджував над складанням ("асоціативний" - це неправильна назва цієї властивості!), Навіть якщо ви дотримуєтесь математичного визначення до букви. Наприклад, (128+128)%256==0але (128%256)+(128%256)==256. Можливо, є хороший заперечення проти вказаної поведінки, але мені не зрозуміло, що це той, про який ви сказали.
Даніель Вагнер

1
@DanielWagner, ти маєш рацію, звичайно, я помиляюсь з "асоціативом". Однак якщо людина зберігає знак дільника і обчислює все в модульній арифметиці, властивість розподілу все-таки має місце; у вашому прикладі ви б мали 256==0. Головне - мати точно Nможливі значення в модульній Nарифметиці, що можливо лише в тому випадку, якщо всі результати знаходяться в діапазоні 0,...,(N-1), а не -(N-1),...,(N-1).
Майкл

6
@Michael: За винятком того, що% не є оператором модуля, це залишок оператора.
Жорен

11

У мене немає телепатичного розуміння міркувань компілятора, але у випадку %є необхідність впоратися з негативними значеннями (і ділити круги до нуля), тоді як &у результаті завжди є нижчі 8 біт.

Ці sarзвуки команд мені , як «зрушення арифметичного права», заповнюючи звільнилися біти зі знаком бітового значення.


0

Математично кажучи, модуль визначається таким чином:

a% b = a - b * підлога (a / b)

Це прямо тут має зрозуміти це для вас. Ми можемо ліквідувати підлогу для цілих чисел, оскільки ціле ділення еквівалентно поверху (a / b). Однак, якби компілятор використав загальний трюк, як ви пропонуєте, він повинен працювати для всіх a і all b. На жаль, це просто не так. Математично кажучи, ваш трюк на 100% правильний для непідписаних цілих чисел (я бачу, що у відповіді зазначено, що підписані цілі числа розбиваються, але я можу підтвердити і не спростувати це, оскільки -a% b має бути позитивним). Однак чи можете ви зробити цей трюк для всіх b? Напевно, ні. Ось чому компілятор цього не робить. Зрештою, якби модуль був легко записаний як одна побітова операція, то ми просто додамо модульну схему, як для додавання, так і інші операції.


4
Думаю, ви плутаєте "підлогу" з "усіченням". Ранні комп’ютери використовували обрізний поділ, тому що часто простіше обчислити, ніж підпорядкований поділ, навіть у випадках, коли речі трапляються рівномірно. Я бачив дуже мало випадків, коли усічений поділ був більш корисним, ніж це було б роздвоєним поділом, але багато мов слідкують за тим, щоб FORTRAN використовував усічений поділ.
supercat

@supercat Математично кажучи, підлога є усіченою. Вони обидва мають однаковий ефект. Вони можуть бути реалізовані однаково в комп'ютері, але вони роблять те саме.
user64742

5
@TheGreatDuck: Вони не однакові для від'ємних чисел. Підлога -2.3є -3, хоча якщо ви скорочуєте -2.3ціле число, то отримаєте -2. Дивіться en.wikipedia.org/wiki/Truncation . "для від'ємних чисел усічення не округляється в тому ж напрямку, що і функція підлоги". А поведінка %негативних чисел є саме причиною того, що ОП бачить описану поведінку.
Марк Дікінсон

@MarkDickinson Я майже впевнений, що модуль в c ++ дає позитивні значення для позитивних дільників, але я не збираюся сперечатися.
user64742

1
@TheGreatDuck - див. Приклад: cpp.sh/3g7h (Зверніть увагу, що C ++ 98 не визначав, який із двох можливих варіантів використовувався, але це зроблено в останніх стандартах, тому, можливо, ви використовували реалізацію C ++ в минулому це робило інакше ...)
Periata Breatta
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.